Path: blob/main/extensions/copilot/test/simulation/fixtures/doc-ts-interface/codeImportPatterns.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { TSESTree } from '@typescript-eslint/experimental-utils';6import * as eslint from 'eslint';7import minimatch from 'minimatch';8import * as path from 'path';9import { createImportRuleListener } from './utils';1011const REPO_ROOT = path.normalize(path.join(__dirname, '../'));1213interface ConditionalPattern {14when?: 'hasBrowser' | 'hasNode' | 'test';15pattern: string;16}1718interface RawImportPatternsConfig {19target: string;20layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';21test?: boolean;22restrictions: string | (string | ConditionalPattern)[];23}2425interface LayerAllowRule {26when: 'hasBrowser' | 'hasNode' | 'test';27allow: string[];28}2930type RawOption = RawImportPatternsConfig | LayerAllowRule;3132function isLayerAllowRule(option: RawOption): option is LayerAllowRule {33return !!((<LayerAllowRule>option).when && (<LayerAllowRule>option).allow);34}3536interface ImportPatternsConfig {37target: string;38restrictions: string[];39}4041export = new class implements eslint.Rule.RuleModule {4243readonly meta: eslint.Rule.RuleMetaData = {44messages: {45badImport: 'Imports violates \'{{restrictions}}\' restrictions. See https://github.com/microsoft/vscode/wiki/Source-Code-Organization',46badFilename: 'Missing definition in `code-import-patterns` for this file. Define rules at https://github.com/microsoft/vscode/blob/main/.eslintrc.json'47},48docs: {49url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'50}51};5253create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {54const options = <RawOption[]>context.options;55const configs = this._processOptions(options);56const relativeFilename = getRelativeFilename(context);5758for (const config of configs) {59if (minimatch(relativeFilename, config.target)) {60return createImportRuleListener((node, value) => this._checkImport(context, config, node, value));61}62}6364context.report({65loc: { line: 1, column: 0 },66messageId: 'badFilename'67});6869return {};70}7172private _optionsCache = new WeakMap<RawOption[], ImportPatternsConfig[]>();7374private _processOptions(options: RawOption[]): ImportPatternsConfig[] {75if (this._optionsCache.has(options)) {76return this._optionsCache.get(options)!;77}7879type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';8081interface ILayerRule {82layer: Layer;83deps: string;84isBrowser?: boolean;85isNode?: boolean;86}8788function orSegment(variants: Layer[]): string {89return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`);90}9192const layerRules: ILayerRule[] = [93{ layer: 'common', deps: orSegment(['common']) },94{ layer: 'worker', deps: orSegment(['common', 'worker']) },95{ layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true },96{ layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true },97{ layer: 'node', deps: orSegment(['common', 'node']), isNode: true },98{ layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true },99];100101let browserAllow: string[] = [];102let nodeAllow: string[] = [];103let testAllow: string[] = [];104for (const option of options) {105if (isLayerAllowRule(option)) {106if (option.when === 'hasBrowser') {107browserAllow = option.allow.slice(0);108} else if (option.when === 'hasNode') {109nodeAllow = option.allow.slice(0);110} else if (option.when === 'test') {111testAllow = option.allow.slice(0);112}113}114}115116function findLayer(layer: Layer): ILayerRule | null {117for (const layerRule of layerRules) {118if (layerRule.layer === layer) {119return layerRule;120}121}122return null;123}124125function generateConfig(layerRule: ILayerRule, target: string, rawRestrictions: (string | ConditionalPattern)[]): [ImportPatternsConfig, ImportPatternsConfig] {126const restrictions: string[] = [];127const testRestrictions: string[] = [...testAllow];128129if (layerRule.isBrowser) {130restrictions.push(...browserAllow);131}132133if (layerRule.isNode) {134restrictions.push(...nodeAllow);135}136137for (const rawRestriction of rawRestrictions) {138let importPattern: string;139let when: 'hasBrowser' | 'hasNode' | 'test' | undefined = undefined;140if (typeof rawRestriction === 'string') {141importPattern = rawRestriction;142} else {143importPattern = rawRestriction.pattern;144when = rawRestriction.when;145}146if (typeof when === 'undefined'147|| (when === 'hasBrowser' && layerRule.isBrowser)148|| (when === 'hasNode' && layerRule.isNode)149) {150restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));151testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));152} else if (when === 'test') {153testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));154testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));155}156}157158testRestrictions.push(...restrictions);159160return [161{162target: target.replace(/\/\~$/, `/${layerRule.layer}/**`),163restrictions: restrictions164},165{166target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`),167restrictions: testRestrictions168}169];170}171172const configs: ImportPatternsConfig[] = [];173for (const option of options) {174if (isLayerAllowRule(option)) {175continue;176}177const target = option.target;178const targetIsVS = /^src\/vs\//.test(target);179const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);180181if (targetIsVS) {182// Always add "vs/nls"183restrictions.push('vs/nls');184}185186if (targetIsVS && option.layer) {187// single layer => simple substitution for /~188const layerRule = findLayer(option.layer);189if (layerRule) {190const [config, testConfig] = generateConfig(layerRule, target, restrictions);191if (option.test) {192configs.push(testConfig);193} else {194configs.push(config);195}196}197} else if (targetIsVS && /\/\~$/.test(target)) {198// generate all layers199for (const layerRule of layerRules) {200const [config, testConfig] = generateConfig(layerRule, target, restrictions);201configs.push(config);202configs.push(testConfig);203}204} else {205configs.push({ target, restrictions: <string[]>restrictions.filter(r => typeof r === 'string') });206}207}208this._optionsCache.set(options, configs);209return configs;210}211212private _checkImport(context: eslint.Rule.RuleContext, config: ImportPatternsConfig, node: TSESTree.Node, importPath: string) {213214// resolve relative paths215if (importPath[0] === '.') {216const relativeFilename = getRelativeFilename(context);217importPath = path.posix.join(path.posix.dirname(relativeFilename), importPath);218if (/^src\/vs\//.test(importPath)) {219// resolve using AMD base url220importPath = importPath.substring('src/'.length);221}222}223224const restrictions = config.restrictions;225226let matched = false;227for (const pattern of restrictions) {228if (minimatch(importPath, pattern)) {229matched = true;230break;231}232}233234if (!matched) {235// None of the restrictions matched236context.report({237loc: node.loc,238messageId: 'badImport',239data: {240restrictions: restrictions.join(' or ')241}242});243}244}245};246247/**248* Returns the filename relative to the project root and using `/` as separators249*/250function getRelativeFilename(context: eslint.Rule.RuleContext): string {251const filename = path.normalize(context.getFilename());252return filename.substring(REPO_ROOT.length).replace(/\\/g, '/');253}254255256