diff --git a/.eslintplugin/code-no-runtime-import.ts b/.eslintplugin/code-no-runtime-import.ts new file mode 100644 index 00000000000..61597236e0c --- /dev/null +++ b/.eslintplugin/code-no-runtime-import.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { dirname, join, relative } from 'path'; +import minimatch from 'minimatch'; +import { createImportRuleListener } from './utils'; + +export = new class implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + layerbreaker: 'You are only allowed to import {{import}} from here using `import type ...`.' + }, + schema: { + type: "array", + items: { + type: "object", + additionalProperties: { + type: "array", + items: { + type: "string" + } + } + } + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + let fileRelativePath = relative(dirname(__dirname), context.getFilename()); + if (!fileRelativePath.endsWith('/')) { + fileRelativePath += '/'; + } + const ruleArgs = >context.options[0]; + + const matchingKey = Object.keys(ruleArgs).find(key => fileRelativePath.startsWith(key) || minimatch(fileRelativePath, key)); + if (!matchingKey) { + // nothing + return {}; + } + + const restrictedImports = ruleArgs[matchingKey]; + return createImportRuleListener((node, path) => { + if (path[0] === '.') { + path = join(dirname(context.getFilename()), path); + } + + if (( + restrictedImports.includes(path) || restrictedImports.some(restriction => minimatch(path, restriction)) + ) && !( + (node.parent?.type === TSESTree.AST_NODE_TYPES.ImportDeclaration && node.parent.importKind === 'type') || + (node.parent && 'exportKind' in node.parent && node.parent.exportKind === 'type'))) { // the export could be multiple types + context.report({ + loc: node.parent!.loc, + messageId: 'layerbreaker', + data: { + import: path + } + }); + } + }); + } +}; diff --git a/.eslintrc.json b/.eslintrc.json index 13cd7274b5e..0922babfe92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1087,6 +1087,19 @@ } ] } + }, + { + "files": [ + "src/vs/workbench/contrib/notebook/browser/view/renderers/*.ts" + ], + "rules": { + "local/code-no-runtime-import": [ + "error", + { + "src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts": ["**/*"] + } + ] + } } ] }