feat - add rule to prevent static imports in critical startup path (#302772)

* feat - add rule to prevent static imports in critical startup path

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Benjamin Pasero
2026-03-18 13:57:46 +01:00
committed by GitHub
parent 331c3ba9c9
commit db0a6b704e
2 changed files with 106 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TSESTree } from '@typescript-eslint/typescript-estree';
import * as eslint from 'eslint';
import { builtinModules } from 'module';
import { join, normalize, relative } from 'path';
import minimatch from 'minimatch';
import { createImportRuleListener } from './utils.ts';
const nodeBuiltins = new Set([
...builtinModules,
...builtinModules.map(m => `node:${m}`)
]);
const REPO_ROOT = normalize(join(import.meta.dirname, '../'));
export default new class implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
staticImport: 'Static imports of \'{{module}}\' are not allowed here because they are loaded synchronously on startup. Use a dynamic `await import(...)` or `import type` instead.'
},
docs: {
description: 'Disallow static imports of node_modules packages to prevent synchronous loading on startup. Allows Node.js built-ins, electron, relative imports, and whitelisted file paths.'
},
schema: {
type: 'array',
items: {
type: 'string'
}
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const allowedPaths = context.options as string[];
const filePath = normalize(relative(REPO_ROOT, normalize(context.getFilename()))).replace(/\\/g, '/');
// Skip whitelisted files
if (allowedPaths.some(pattern => filePath === pattern || minimatch(filePath, pattern))) {
return {};
}
return createImportRuleListener((node, value) => {
// Allow `import type` and `export type` declarations
if (node.parent?.type === TSESTree.AST_NODE_TYPES.ImportDeclaration && node.parent.importKind === 'type') {
return;
}
if (node.parent && 'exportKind' in node.parent && node.parent.exportKind === 'type') {
return;
}
// Allow relative imports
if (value.startsWith('.')) {
return;
}
// Allow Node.js built-in modules
if (nodeBuiltins.has(value)) {
return;
}
// Allow electron
if (value === 'electron') {
return;
}
context.report({
loc: node.parent!.loc,
messageId: 'staticImport',
data: {
module: value
}
});
});
}
};

View File

@@ -1042,6 +1042,33 @@ export default tseslint.config(
]
}
},
// electron-main layer: prevent static imports of heavy node_modules
// that would be synchronously loaded on startup
{
files: [
'src/vs/code/electron-main/**/*.ts',
'src/vs/code/node/**/*.ts',
'src/vs/platform/*/electron-main/**/*.ts',
'src/vs/platform/*/node/**/*.ts',
],
languageOptions: {
parser: tseslint.parser,
},
plugins: {
'local': pluginLocal,
},
rules: {
'local/code-no-static-node-module-import': [
'error',
// Files that run in separate processes, not on the electron-main startup path
'src/vs/platform/agentHost/node/copilot/**/*.ts',
'src/vs/platform/files/node/watcher/**/*.ts',
'src/vs/platform/terminal/node/**/*.ts',
// Files that use small, safe modules
'src/vs/platform/environment/node/argv.ts',
]
}
},
// browser/electron-browser layer
{
files: [