Add /~ as a layer template, adopt it, simplify and align code import rules (#140857)

This commit is contained in:
Alex Dima
2022-01-18 18:26:43 +01:00
parent aa86df2561
commit 62b0d5cb81
12 changed files with 451 additions and 902 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -29,16 +29,8 @@ module.exports = new class {
}
create(context) {
const options = context.options;
const { configs, warnings } = this._processOptions(options);
const configs = this._processOptions(options);
const relativeFilename = getRelativeFilename(context);
if (warnings.length > 0) {
// configuration warnings
context.report({
loc: { line: 1, column: 0 },
message: warnings.join('\n')
});
return {};
}
for (const config of configs) {
if (minimatch(relativeFilename, config.target)) {
return (0, utils_1.createImportRuleListener)((node, value) => this._checkImport(context, config, node, value));
@@ -54,24 +46,104 @@ module.exports = new class {
if (this._optionsCache.has(options)) {
return this._optionsCache.get(options);
}
function orSegment(variants) {
return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`);
}
const layerRules = [
{ layer: 'common', deps: orSegment(['common']) },
{ layer: 'worker', deps: orSegment(['common', 'worker']) },
{ layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true },
{ layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true },
{ layer: 'node', deps: orSegment(['common', 'node']), isNode: true },
{ layer: 'electron-browser', deps: orSegment(['common', 'browser', 'node', 'electron-sandbox', 'electron-browser']), isBrowser: true, isNode: true },
{ layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true },
];
function findLayer(layer) {
for (const layerRule of layerRules) {
if (layerRule.layer === layer) {
return layerRule;
}
}
return null;
}
function generateConfig(layerRule, target, rawRestrictions) {
const restrictions = [];
const testRestrictions = ['assert', 'sinon', 'sinon-test'];
if (layerRule.isBrowser) {
restrictions.push('vs/css!./**/*');
}
if (layerRule.isNode) {
restrictions.push('@microsoft/*', '@vscode/*', '@parcel/*', '*');
}
for (const rawRestriction of rawRestrictions) {
let importPattern;
let when = undefined;
if (typeof rawRestriction === 'string') {
importPattern = rawRestriction;
}
else {
importPattern = rawRestriction.pattern;
when = rawRestriction.when;
}
if (typeof when === 'undefined'
|| (when === 'hasBrowser' && layerRule.isBrowser)
|| (when === 'hasNode' && layerRule.isNode)) {
restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
}
else if (when === 'test') {
testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
}
}
testRestrictions.push(...restrictions);
return [
{
target: target.replace(/\/\~$/, `/${layerRule.layer}/**`),
restrictions: restrictions
},
{
target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`),
restrictions: testRestrictions
}
];
}
const configs = [];
const warnings = [];
for (const option of options) {
const target = option.target;
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions.slice(0));
if (/^src\/vs\/.*\/\*\*/.test(target)) {
const amdTarget = target.substring('src/'.length);
// Allow importing itself
if (restrictions.includes(amdTarget)) {
warnings.push(`target ${target}: '${amdTarget}' is automatically included in restrictions.`);
}
restrictions.push(amdTarget);
const targetIsVS = /^src\/vs\//.test(target);
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);
if (targetIsVS) {
// Always add "vs/nls"
restrictions.push('vs/nls');
}
if (targetIsVS && option.layer) {
// single layer => simple substitution for /~
const layerRule = findLayer(option.layer);
if (layerRule) {
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
if (option.test) {
configs.push(testConfig);
}
else {
configs.push(config);
}
}
}
else if (targetIsVS && /\/\~$/.test(target)) {
// generate all layers
for (const layerRule of layerRules) {
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
configs.push(config);
configs.push(testConfig);
}
}
else {
configs.push({ target, restrictions: restrictions.filter(r => typeof r === 'string') });
}
configs.push({ target, restrictions });
}
const result = { configs, warnings };
this._optionsCache.set(options, result);
return result;
this._optionsCache.set(options, configs);
return configs;
}
_checkImport(context, config, node, importPath) {
// resolve relative paths

View File

@@ -11,9 +11,16 @@ import { createImportRuleListener } from './utils';
const REPO_ROOT = path.normalize(path.join(__dirname, '../../../'));
interface ConditionalPattern {
when?: 'hasBrowser' | 'hasNode' | 'test';
pattern: string;
}
interface RawImportPatternsConfig {
target: string;
restrictions: string | string[];
layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-browser' | 'electron-main';
test?: boolean;
restrictions: string | (string | ConditionalPattern)[];
}
interface ImportPatternsConfig {
@@ -35,18 +42,9 @@ export = new class implements eslint.Rule.RuleModule {
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const options = <RawImportPatternsConfig[]>context.options;
const { configs, warnings } = this._processOptions(options);
const configs = this._processOptions(options);
const relativeFilename = getRelativeFilename(context);
if (warnings.length > 0) {
// configuration warnings
context.report({
loc: { line: 1, column: 0 },
message: warnings.join('\n')
});
return {};
}
for (const config of configs) {
if (minimatch(relativeFilename, config.target)) {
return createImportRuleListener((node, value) => this._checkImport(context, config, node, value));
@@ -61,30 +59,127 @@ export = new class implements eslint.Rule.RuleModule {
return {};
}
private _optionsCache = new WeakMap<RawImportPatternsConfig[], { configs: ImportPatternsConfig[]; warnings: string[]; }>();
private _optionsCache = new WeakMap<RawImportPatternsConfig[], ImportPatternsConfig[]>();
private _processOptions(options: RawImportPatternsConfig[]): { configs: ImportPatternsConfig[]; warnings: string[]; } {
private _processOptions(options: RawImportPatternsConfig[]): ImportPatternsConfig[] {
if (this._optionsCache.has(options)) {
return this._optionsCache.get(options)!;
}
type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-browser' | 'electron-main';
interface ILayerRule {
layer: Layer;
deps: string;
isBrowser?: boolean;
isNode?: boolean;
}
function orSegment(variants: Layer[]): string {
return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`);
}
const layerRules: ILayerRule[] = [
{ layer: 'common', deps: orSegment(['common']) },
{ layer: 'worker', deps: orSegment(['common', 'worker']) },
{ layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true },
{ layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true },
{ layer: 'node', deps: orSegment(['common', 'node']), isNode: true },
{ layer: 'electron-browser', deps: orSegment(['common', 'browser', 'node', 'electron-sandbox', 'electron-browser']), isBrowser: true, isNode: true },
{ layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true },
];
function findLayer(layer: Layer): ILayerRule | null {
for (const layerRule of layerRules) {
if (layerRule.layer === layer) {
return layerRule;
}
}
return null;
}
function generateConfig(layerRule: ILayerRule, target: string, rawRestrictions: (string | ConditionalPattern)[]): [ImportPatternsConfig, ImportPatternsConfig] {
const restrictions: string[] = [];
const testRestrictions: string[] = ['assert', 'sinon', 'sinon-test'];
if (layerRule.isBrowser) {
restrictions.push('vs/css!./**/*');
}
if (layerRule.isNode) {
restrictions.push('@microsoft/*', '@vscode/*', '@parcel/*', '*');
}
for (const rawRestriction of rawRestrictions) {
let importPattern: string;
let when: 'hasBrowser' | 'hasNode' | 'test' | undefined = undefined;
if (typeof rawRestriction === 'string') {
importPattern = rawRestriction;
} else {
importPattern = rawRestriction.pattern;
when = rawRestriction.when;
}
if (typeof when === 'undefined'
|| (when === 'hasBrowser' && layerRule.isBrowser)
|| (when === 'hasNode' && layerRule.isNode)
) {
restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
} else if (when === 'test') {
testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`));
testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`));
}
}
testRestrictions.push(...restrictions);
return [
{
target: target.replace(/\/\~$/, `/${layerRule.layer}/**`),
restrictions: restrictions
},
{
target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`),
restrictions: testRestrictions
}
];
}
const configs: ImportPatternsConfig[] = [];
const warnings: string[] = [];
for (const option of options) {
const target = option.target;
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions.slice(0));
if (/^src\/vs\/.*\/\*\*/.test(target)) {
const amdTarget = target.substring('src/'.length);
// Allow importing itself
if (restrictions.includes(amdTarget)) {
warnings.push(`target ${target}: '${amdTarget}' is automatically included in restrictions.`);
}
restrictions.push(amdTarget);
const targetIsVS = /^src\/vs\//.test(target);
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);
if (targetIsVS) {
// Always add "vs/nls"
restrictions.push('vs/nls');
}
if (targetIsVS && option.layer) {
// single layer => simple substitution for /~
const layerRule = findLayer(option.layer);
if (layerRule) {
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
if (option.test) {
configs.push(testConfig);
} else {
configs.push(config);
}
}
} else if (targetIsVS && /\/\~$/.test(target)) {
// generate all layers
for (const layerRule of layerRules) {
const [config, testConfig] = generateConfig(layerRule, target, restrictions);
configs.push(config);
configs.push(testConfig);
}
} else {
configs.push({ target, restrictions: <string[]>restrictions.filter(r => typeof r === 'string') });
}
configs.push({ target, restrictions });
}
const result = { configs, warnings };
this._optionsCache.set(options, result);
return result;
this._optionsCache.set(options, configs);
return configs;
}
private _checkImport(context: eslint.Rule.RuleContext, config: ImportPatternsConfig, node: TSESTree.Node, importPath: string) {

View File

@@ -16,6 +16,8 @@ import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNode
import { flakySuite } from 'vs/base/test/common/testUtils';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import product from 'vs/platform/product/common/product';
class MessageStream extends Disposable {

View File

@@ -8,6 +8,8 @@ import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/lis
import { List } from 'vs/base/browser/ui/list/listWidget';
import { QuickInputController } from 'vs/base/parts/quickinput/browser/quickInput';
import { IQuickPick, IQuickPickItem } from 'vs/base/parts/quickinput/common/quickInput';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { IWorkbenchListOptions } from 'vs/platform/list/browser/listService';
// Simple promisify of setTimeout

View File

@@ -7,7 +7,11 @@ import { flatten } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { Disposable } from 'vs/base/common/lifecycle';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { codeActionCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/codeAction';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
import * as nls from 'vs/nls';
import { Extensions, IConfigurationNode, IConfigurationRegistry, ConfigurationScope, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';

View File

@@ -9,6 +9,8 @@ import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/languages';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';

View File

@@ -29,6 +29,8 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { isStringArray } from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
namespace snippetExt {

View File

@@ -6,6 +6,8 @@
import { URI } from 'vs/base/common/uri';
import { TestItemImpl } from 'vs/workbench/api/common/extHostTestingPrivateApi';
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import { TestSingleUseCollection } from 'vs/workbench/contrib/testing/test/common/ownedTestCollection';
export * as Convert from 'vs/workbench/api/common/extHostTypeConverters';

View File

@@ -6,6 +6,8 @@
import { refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
// TODO@layers
// eslint-disable-next-line code-import-patterns
import type { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api';
import { URI } from 'vs/base/common/uri';

View File

@@ -5,7 +5,7 @@
import { Readable, ReadableStream, newWriteableStream, listenStream } from 'vs/base/common/stream';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
import { IDisposable } from 'vs/workbench/workbench.web.api';
import { IDisposable } from 'vs/base/common/lifecycle';
export const UTF8 = 'utf8';
export const UTF8_with_bom = 'utf8bom';