eae5d8c807
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
284 lines
9.8 KiB
JavaScript
284 lines
9.8 KiB
JavaScript
"use strict";
|
|
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const ts = require("typescript");
|
|
const fs_1 = require("fs");
|
|
const path_1 = require("path");
|
|
const minimatch_1 = require("minimatch");
|
|
//
|
|
// #############################################################################################
|
|
//
|
|
// A custom typescript checker for the specific task of detecting the use of certain types in a
|
|
// layer that does not allow such use. For example:
|
|
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
|
|
// - using node.js globals in common/browser layer (e.g. process)
|
|
//
|
|
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
|
|
//
|
|
// #############################################################################################
|
|
//
|
|
// Types we assume are present in all implementations of JS VMs (node.js, browsers)
|
|
// Feel free to add more core types as you see needed if present in node.js and browsers
|
|
const CORE_TYPES = [
|
|
'require',
|
|
'setTimeout',
|
|
'clearTimeout',
|
|
'setInterval',
|
|
'clearInterval',
|
|
'console',
|
|
'log',
|
|
'info',
|
|
'warn',
|
|
'error',
|
|
'group',
|
|
'groupEnd',
|
|
'table',
|
|
'assert',
|
|
'Error',
|
|
'String',
|
|
'throws',
|
|
'stack',
|
|
'captureStackTrace',
|
|
'stackTraceLimit',
|
|
'TextDecoder',
|
|
'TextEncoder',
|
|
'encode',
|
|
'decode',
|
|
'self',
|
|
'trimLeft',
|
|
'trimRight'
|
|
];
|
|
// Types that are defined in a common layer but are known to be only
|
|
// available in native environments should not be allowed in browser
|
|
const NATIVE_TYPES = [
|
|
'NativeParsedArgs',
|
|
'INativeEnvironmentService',
|
|
'INativeWindowConfiguration',
|
|
'ICommonNativeHostService'
|
|
];
|
|
const RULES = [
|
|
// Tests: skip
|
|
{
|
|
target: '**/vs/**/test/**',
|
|
skip: true // -> skip all test files
|
|
},
|
|
// Common: vs/base/common/platform.ts
|
|
{
|
|
target: '**/vs/base/common/platform.ts',
|
|
allowedTypes: [
|
|
...CORE_TYPES,
|
|
// Safe access to postMessage() and friends
|
|
'MessageEvent',
|
|
'data'
|
|
],
|
|
disallowedTypes: NATIVE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common: vs/platform/environment/common/argv.ts
|
|
{
|
|
target: '**/vs/platform/environment/common/argv.ts',
|
|
disallowedTypes: [ /* Ignore native types that are defined from here */],
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common: vs/platform/environment/common/environment.ts
|
|
{
|
|
target: '**/vs/platform/environment/common/environment.ts',
|
|
disallowedTypes: [ /* Ignore native types that are defined from here */],
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common: vs/platform/windows/common/windows.ts
|
|
{
|
|
target: '**/vs/platform/windows/common/windows.ts',
|
|
disallowedTypes: [ /* Ignore native types that are defined from here */],
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common: vs/platform/native/common/native.ts
|
|
{
|
|
target: '**/vs/platform/native/common/native.ts',
|
|
disallowedTypes: [ /* Ignore native types that are defined from here */],
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common: vs/workbench/api/common/extHostExtensionService.ts
|
|
{
|
|
target: '**/vs/workbench/api/common/extHostExtensionService.ts',
|
|
allowedTypes: [
|
|
...CORE_TYPES,
|
|
// Safe access to global
|
|
'global'
|
|
],
|
|
disallowedTypes: NATIVE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Common
|
|
{
|
|
target: '**/vs/**/common/**',
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedTypes: NATIVE_TYPES,
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts',
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Browser
|
|
{
|
|
target: '**/vs/**/browser/**',
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedTypes: NATIVE_TYPES,
|
|
disallowedDefinitions: [
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Browser (editor contrib)
|
|
{
|
|
target: '**/src/vs/editor/contrib/**',
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedTypes: NATIVE_TYPES,
|
|
disallowedDefinitions: [
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// node.js
|
|
{
|
|
target: '**/vs/**/node/**',
|
|
allowedTypes: [
|
|
...CORE_TYPES,
|
|
// --> types from node.d.ts that duplicate from lib.dom.d.ts
|
|
'URL',
|
|
'protocol',
|
|
'hostname',
|
|
'port',
|
|
'pathname',
|
|
'search',
|
|
'username',
|
|
'password'
|
|
],
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts' // no DOM
|
|
]
|
|
},
|
|
// Electron (sandbox)
|
|
{
|
|
target: '**/vs/**/electron-sandbox/**',
|
|
allowedTypes: CORE_TYPES,
|
|
disallowedDefinitions: [
|
|
'@types/node' // no node.js
|
|
]
|
|
},
|
|
// Electron (renderer): skip
|
|
{
|
|
target: '**/vs/**/electron-browser/**',
|
|
skip: true // -> supports all types
|
|
},
|
|
// Electron (main)
|
|
{
|
|
target: '**/vs/**/electron-main/**',
|
|
allowedTypes: [
|
|
...CORE_TYPES,
|
|
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
|
|
'Event',
|
|
'Request'
|
|
],
|
|
disallowedDefinitions: [
|
|
'lib.dom.d.ts' // no DOM
|
|
]
|
|
}
|
|
];
|
|
const TS_CONFIG_PATH = path_1.join(__dirname, '../../', 'src', 'tsconfig.json');
|
|
let hasErrors = false;
|
|
function checkFile(program, sourceFile, rule) {
|
|
checkNode(sourceFile);
|
|
function checkNode(node) {
|
|
var _a, _b;
|
|
if (node.kind !== ts.SyntaxKind.Identifier) {
|
|
return ts.forEachChild(node, checkNode); // recurse down
|
|
}
|
|
const text = node.getText(sourceFile);
|
|
if ((_a = rule.allowedTypes) === null || _a === void 0 ? void 0 : _a.some(allowed => allowed === text)) {
|
|
return; // override
|
|
}
|
|
if ((_b = rule.disallowedTypes) === null || _b === void 0 ? void 0 : _b.some(disallowed => disallowed === text)) {
|
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
|
|
hasErrors = true;
|
|
return;
|
|
}
|
|
const checker = program.getTypeChecker();
|
|
const symbol = checker.getSymbolAtLocation(node);
|
|
if (symbol) {
|
|
const declarations = symbol.declarations;
|
|
if (Array.isArray(declarations)) {
|
|
for (const declaration of declarations) {
|
|
if (declaration) {
|
|
const parent = declaration.parent;
|
|
if (parent) {
|
|
const parentSourceFile = parent.getSourceFile();
|
|
if (parentSourceFile) {
|
|
const definitionFileName = parentSourceFile.fileName;
|
|
if (rule.disallowedDefinitions) {
|
|
for (const disallowedDefinition of rule.disallowedDefinitions) {
|
|
if (definitionFileName.indexOf(disallowedDefinition) >= 0) {
|
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
|
|
hasErrors = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function createProgram(tsconfigPath) {
|
|
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
const configHostParser = { fileExists: fs_1.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs_1.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
|
|
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path_1.resolve(path_1.dirname(tsconfigPath)), { noEmit: true });
|
|
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
|
|
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
|
|
}
|
|
//
|
|
// Create program and start checking
|
|
//
|
|
const program = createProgram(TS_CONFIG_PATH);
|
|
for (const sourceFile of program.getSourceFiles()) {
|
|
for (const rule of RULES) {
|
|
if (minimatch_1.match([sourceFile.fileName], rule.target).length > 0) {
|
|
if (!rule.skip) {
|
|
checkFile(program, sourceFile, rule);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hasErrors) {
|
|
process.exit(1);
|
|
}
|