Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/fixtures/doc-ts-interface/codeImportPatterns.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { TSESTree } from '@typescript-eslint/experimental-utils';
7
import * as eslint from 'eslint';
8
import minimatch from 'minimatch';
9
import * as path from 'path';
10
import { createImportRuleListener } from './utils';
11
12
const REPO_ROOT = path.normalize(path.join(__dirname, '../'));
13
14
interface ConditionalPattern {
15
when?: 'hasBrowser' | 'hasNode' | 'test';
16
pattern: string;
17
}
18
19
interface RawImportPatternsConfig {
20
target: string;
21
layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';
22
test?: boolean;
23
restrictions: string | (string | ConditionalPattern)[];
24
}
25
26
interface LayerAllowRule {
27
when: 'hasBrowser' | 'hasNode' | 'test';
28
allow: string[];
29
}
30
31
type RawOption = RawImportPatternsConfig | LayerAllowRule;
32
33
function isLayerAllowRule(option: RawOption): option is LayerAllowRule {
34
return !!((<LayerAllowRule>option).when && (<LayerAllowRule>option).allow);
35
}
36
37
interface ImportPatternsConfig {
38
target: string;
39
restrictions: string[];
40
}
41
42
export = new class implements eslint.Rule.RuleModule {
43
44
readonly meta: eslint.Rule.RuleMetaData = {
45
messages: {
46
badImport: 'Imports violates \'{{restrictions}}\' restrictions. See https://github.com/microsoft/vscode/wiki/Source-Code-Organization',
47
badFilename: 'Missing definition in `code-import-patterns` for this file. Define rules at https://github.com/microsoft/vscode/blob/main/.eslintrc.json'
48
},
49
docs: {
50
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
51
}
52
};
53
54
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
55
const options = <RawOption[]>context.options;
56
const configs = this._processOptions(options);
57
const relativeFilename = getRelativeFilename(context);
58
59
for (const config of configs) {
60
if (minimatch(relativeFilename, config.target)) {
61
return createImportRuleListener((node, value) => this._checkImport(context, config, node, value));
62
}
63
}
64
65
context.report({
66
loc: { line: 1, column: 0 },
67
messageId: 'badFilename'
68
});
69
70
return {};
71
}
72
73
private _optionsCache = new WeakMap<RawOption[], ImportPatternsConfig[]>();
74
75
private _processOptions(options: RawOption[]): ImportPatternsConfig[] {
76
if (this._optionsCache.has(options)) {
77
return this._optionsCache.get(options)!;
78
}
79
80
type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main';
81
82
interface ILayerRule {
83
layer: Layer;
84
deps: string;
85
isBrowser?: boolean;
86
isNode?: boolean;
87
}
88
89
function orSegment(variants: Layer[]): string {
90
return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`);
91
}
92
93
const layerRules: ILayerRule[] = [
94
{ layer: 'common', deps: orSegment(['common']) },
95
{ layer: 'worker', deps: orSegment(['common', 'worker']) },
96
{ layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true },
97
{ layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true },
98
{ layer: 'node', deps: orSegment(['common', 'node']), isNode: true },
99
{ layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true },
100
];
101
102
let browserAllow: string[] = [];
103
let nodeAllow: string[] = [];
104
let testAllow: string[] = [];
105
for (const option of options) {
106
if (isLayerAllowRule(option)) {
107
if (option.when === 'hasBrowser') {
108
browserAllow = option.allow.slice(0);
109
} else if (option.when === 'hasNode') {
110
nodeAllow = option.allow.slice(0);
111
} else if (option.when === 'test') {
112
testAllow = option.allow.slice(0);
113
}
114
}
115
}
116
117
function findLayer(layer: Layer): ILayerRule | null {
118
for (const layerRule of layerRules) {
119
if (layerRule.layer === layer) {
120
return layerRule;
121
}
122
}
123
return null;
124
}
125
126
function generateConfig(layerRule: ILayerRule, target: string, rawRestrictions: (string | ConditionalPattern)[]): [ImportPatternsConfig, ImportPatternsConfig] {
127
const restrictions: string[] = [];
128
const testRestrictions: string[] = [...testAllow];
129
130
if (layerRule.isBrowser) {
131
restrictions.push(...browserAllow);
132
}
133
134
if (layerRule.isNode) {
135
restrictions.push(...nodeAllow);
136
}
137
138
for (const rawRestriction of rawRestrictions) {
139
let importPattern: string;
140
let when: 'hasBrowser' | 'hasNode' | 'test' | undefined = undefined;
141
if (typeof rawRestriction === 'string') {
142
importPattern = rawRestriction;
143
} else {
144
importPattern = rawRestriction.pattern;
145
when = rawRestriction.when;
146
}
147
if (typeof when === 'undefined'
148
|| (when === 'hasBrowser' && layerRule.isBrowser)
149
|| (when === 'hasNode' && layerRule.isNode)
150
) {
151
restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
152
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
153
} else if (when === 'test') {
154
testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
155
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
156
}
157
}
158
159
testRestrictions.push(...restrictions);
160
161
return [
162
{
163
target: target.replace(/\/\~$/, `/${layerRule.layer}/**`),
164
restrictions: restrictions
165
},
166
{
167
target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`),
168
restrictions: testRestrictions
169
}
170
];
171
}
172
173
const configs: ImportPatternsConfig[] = [];
174
for (const option of options) {
175
if (isLayerAllowRule(option)) {
176
continue;
177
}
178
const target = option.target;
179
const targetIsVS = /^src\/vs\//.test(target);
180
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);
181
182
if (targetIsVS) {
183
// Always add "vs/nls"
184
restrictions.push('vs/nls');
185
}
186
187
if (targetIsVS && option.layer) {
188
// single layer => simple substitution for /~
189
const layerRule = findLayer(option.layer);
190
if (layerRule) {
191
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
192
if (option.test) {
193
configs.push(testConfig);
194
} else {
195
configs.push(config);
196
}
197
}
198
} else if (targetIsVS && /\/\~$/.test(target)) {
199
// generate all layers
200
for (const layerRule of layerRules) {
201
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
202
configs.push(config);
203
configs.push(testConfig);
204
}
205
} else {
206
configs.push({ target, restrictions: <string[]>restrictions.filter(r => typeof r === 'string') });
207
}
208
}
209
this._optionsCache.set(options, configs);
210
return configs;
211
}
212
213
private _checkImport(context: eslint.Rule.RuleContext, config: ImportPatternsConfig, node: TSESTree.Node, importPath: string) {
214
215
// resolve relative paths
216
if (importPath[0] === '.') {
217
const relativeFilename = getRelativeFilename(context);
218
importPath = path.posix.join(path.posix.dirname(relativeFilename), importPath);
219
if (/^src\/vs\//.test(importPath)) {
220
// resolve using AMD base url
221
importPath = importPath.substring('src/'.length);
222
}
223
}
224
225
const restrictions = config.restrictions;
226
227
let matched = false;
228
for (const pattern of restrictions) {
229
if (minimatch(importPath, pattern)) {
230
matched = true;
231
break;
232
}
233
}
234
235
if (!matched) {
236
// None of the restrictions matched
237
context.report({
238
loc: node.loc,
239
messageId: 'badImport',
240
data: {
241
restrictions: restrictions.join(' or ')
242
}
243
});
244
}
245
}
246
};
247
248
/**
249
* Returns the filename relative to the project root and using `/` as separators
250
*/
251
function getRelativeFilename(context: eslint.Rule.RuleContext): string {
252
const filename = path.normalize(context.getFilename());
253
return filename.substring(REPO_ROOT.length).replace(/\\/g, '/');
254
}
255
256