mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-15 02:07:31 +01:00
The check took ~257s and would sometimes run out of memory. Two separate bottlenecks were responsible. layersChecker.ts built a full in-process ts.Program (15s, 2GB) and then asked the type checker for a symbol at every property access in the program, ~2.5M identifiers. Each lookup materialized symbol and type objects, pushing the heap past 3.6GB, and the alias/containment chain was then walked per symbol. It now runs on the TS7 native API that the repo already vendors for build/lib/tsgo.ts: - updateSnapshot replaces createProgram, so the program lives in the compiler rather than the V8 heap - the disallowed types are resolved to symbols once up front, turning the per-reference check into a local id lookup - symbol lookups are batched instead of issued one node at a time - property accesses are prefiltered by member name, so only those that could name a member of a disallowed type are resolved at all That brings it from 48.6s and 3.8GB peak to 11.7s and 0.8GB peak. The six tsconfig.<layer>.json projects were type checked serially with TS6, which accounted for the remaining ~208s. They move to the native compiler and run in parallel in the new layersTypeCheck.ts, taking ~11s. Concurrency is derived from free memory (the largest project peaks at ~3.5GB) so a memory-constrained machine falls back towards serial execution rather than swapping. Verified to report byte-identical violations, including line and column, against a seeded set covering direct references, import aliases, inherited members, nested member access and ipcMain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import assert from 'assert';
|
|
import { afterEach, beforeEach, suite, test } from 'node:test';
|
|
import { mkdtempSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'fs';
|
|
import { tmpdir } from 'os';
|
|
import { dirname, join } from 'path';
|
|
import { checkLayers, getRule, type ILayerViolation, type IRule } from '../../checker/layersChecker.ts';
|
|
|
|
suite('layersChecker', () => {
|
|
let rootPath: string;
|
|
let tsconfigPath: string;
|
|
|
|
const rules: IRule[] = [
|
|
{ target: '**/test/**', skip: true },
|
|
{ target: '**/browser/**', disallowedTypes: ['ForbiddenService'] },
|
|
];
|
|
|
|
beforeEach(() => {
|
|
rootPath = realpathSync(mkdtempSync(join(tmpdir(), '.layers-checker-')));
|
|
tsconfigPath = join(rootPath, 'tsconfig.json');
|
|
writeFileSync(tsconfigPath, JSON.stringify({
|
|
compilerOptions: {
|
|
module: 'nodenext',
|
|
moduleResolution: 'nodenext',
|
|
noEmit: true,
|
|
skipLibCheck: true,
|
|
},
|
|
include: ['**/*.ts'],
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
rmSync(rootPath, { recursive: true, force: true });
|
|
});
|
|
|
|
test('matches rules relative to a hidden parent path', () => {
|
|
const fileName = join(rootPath, 'vs', 'feature', 'browser', 'feature.ts');
|
|
|
|
assert.strictEqual(getRule(fileName, rootPath, rules), rules[1]);
|
|
});
|
|
|
|
test('ignores skipped files', async () => {
|
|
writeForbiddenService();
|
|
writeSource('vs/feature/browser/test/feature.test.ts', `
|
|
import { ForbiddenService } from '../../../platform/common/service.js';
|
|
export const service: ForbiddenService | undefined = undefined;
|
|
`);
|
|
|
|
assert.deepStrictEqual(await getViolations(), []);
|
|
});
|
|
|
|
test('detects aliased forbidden types', async () => {
|
|
writeForbiddenService();
|
|
writeSource('vs/feature/browser/feature.ts', `
|
|
import { ForbiddenService as Service } from '../../platform/common/service.js';
|
|
export const service: Service | undefined = undefined;
|
|
`);
|
|
|
|
assert.deepStrictEqual(await getViolations(), [
|
|
{ type: 'ForbiddenService', fileName: 'vs/feature/browser/feature.ts', line: 2, character: 13 },
|
|
{ type: 'ForbiddenService', fileName: 'vs/feature/browser/feature.ts', line: 2, character: 33 },
|
|
{ type: 'ForbiddenService', fileName: 'vs/feature/browser/feature.ts', line: 3, character: 26 },
|
|
]);
|
|
});
|
|
|
|
test('detects members of inferred forbidden types', async () => {
|
|
writeForbiddenService();
|
|
writeSource('vs/feature/browser/feature.ts', `
|
|
import { getService } from '../../platform/common/service.js';
|
|
getService().run();
|
|
`);
|
|
|
|
assert.deepStrictEqual(await getViolations(), [
|
|
{ type: 'ForbiddenService', fileName: 'vs/feature/browser/feature.ts', line: 3, character: 17 },
|
|
]);
|
|
});
|
|
|
|
function writeForbiddenService(): void {
|
|
writeSource('vs/platform/common/service.ts', `
|
|
export interface ForbiddenService { run(): void }
|
|
export declare function getService(): ForbiddenService;
|
|
`);
|
|
}
|
|
|
|
function writeSource(relativePath: string, contents: string): string {
|
|
const fileName = join(rootPath, relativePath);
|
|
mkdirSync(dirname(fileName), { recursive: true });
|
|
writeFileSync(fileName, contents);
|
|
return fileName;
|
|
}
|
|
|
|
async function getViolations(): Promise<Pick<ILayerViolation, 'type' | 'fileName' | 'line' | 'character'>[]> {
|
|
const violations = await checkLayers(tsconfigPath, rules);
|
|
return violations
|
|
.map(violation => ({
|
|
type: violation.type,
|
|
fileName: violation.fileName.slice(rootPath.length + 1).replaceAll('\\', '/'),
|
|
line: violation.line,
|
|
character: violation.character,
|
|
}))
|
|
.sort((a, b) => a.line - b.line || a.character - b.character);
|
|
}
|
|
});
|