2020-01-07 10:03:59 +00:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for license information .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
import * as ts from 'typescript' ;
import { readFileSync , existsSync } from 'fs' ;
2020-01-07 10:10:01 +00:00
import { resolve , dirname , join } from 'path' ;
2020-01-07 10:03:59 +00:00
import { match } from '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' , // from our AMD loader
'setTimeout' ,
'clearTimeout' ,
'setInterval' ,
'clearInterval' ,
'console' ,
2022-04-04 13:04:29 +00:00
'Console' ,
2020-01-07 10:03:59 +00:00
'Error' ,
2022-04-04 13:04:29 +00:00
'ErrorConstructor' ,
2020-01-07 10:03:59 +00:00
'String' ,
'TextDecoder' ,
'TextEncoder' ,
'self' ,
2022-01-05 10:11:12 +00:00
'queueMicrotask' ,
2022-03-10 15:51:37 +00:00
'Array' ,
'Uint8Array' ,
'Uint16Array' ,
'Uint32Array' ,
'Int8Array' ,
'Int16Array' ,
'Int32Array' ,
'Float32Array' ,
'Float64Array' ,
'Uint8ClampedArray' ,
'BigUint64Array' ,
'BigInt64Array' ,
'btoa' ,
'atob' ,
2022-08-04 23:26:18 +00:00
'AbortController' ,
2022-03-10 15:51:37 +00:00
'AbortSignal' ,
2022-01-05 10:11:12 +00:00
'MessageChannel' ,
2022-04-04 09:21:57 +00:00
'MessagePort' ,
'URL' ,
2022-10-06 19:08:45 +00:00
'URLSearchParams' ,
'ReadonlyArray' ,
2023-08-07 12:46:27 +00:00
'Event' ,
'EventTarget' ,
'BroadcastChannel' ,
'performance' ,
'Blob'
2020-01-07 10:03:59 +00:00
] ;
2020-09-04 08:32:00 +00:00
// 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' ,
2021-03-15 10:27:31 +00:00
'AbstractNativeEnvironmentService' ,
2020-09-17 13:43:15 +00:00
'INativeWindowConfiguration' ,
2023-02-25 14:49:47 +00:00
'ICommonNativeHostService' ,
'INativeHostService' ,
'IMainProcessService'
2020-09-04 08:32:00 +00:00
] ;
2022-04-12 05:46:17 +00:00
const RULES : IRule [ ] = [
2020-01-07 10:03:59 +00:00
// 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' ,
] ,
2020-09-04 08:32:00 +00:00
disallowedTypes : NATIVE_TYPES ,
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
'@types/node' // no node.js
]
} ,
2023-11-07 11:52:20 +00:00
// Common: vs/base/common/async.ts
{
target : '**/vs/base/common/async.ts' ,
allowedTypes : [
. . . CORE_TYPES ,
// Safe access to requestIdleCallback & cancelIdleCallback
'requestIdleCallback' ,
'cancelIdleCallback'
] ,
disallowedTypes : NATIVE_TYPES ,
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
'@types/node' // no node.js
]
} ,
2021-03-15 10:27:31 +00:00
// Common: vs/platform/environment/common/*
2020-09-04 08:32:00 +00:00
{
2021-03-15 10:27:31 +00:00
target : '**/vs/platform/environment/common/*.ts' ,
2020-09-04 08:32:00 +00:00
allowedTypes : CORE_TYPES ,
2022-02-08 19:09:00 +00:00
disallowedTypes : [ /* Ignore native types that are defined from here */ ] ,
2020-09-04 08:32:00 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
'@types/node' // no node.js
]
} ,
2022-02-14 07:41:24 +00:00
// Common: vs/platform/window/common/window.ts
2020-09-04 08:32:00 +00:00
{
2022-02-14 07:41:24 +00:00
target : '**/vs/platform/window/common/window.ts' ,
2020-09-04 08:32:00 +00:00
allowedTypes : CORE_TYPES ,
2022-02-08 19:09:00 +00:00
disallowedTypes : [ /* Ignore native types that are defined from here */ ] ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
2020-01-07 10:10:01 +00:00
'@types/node' // no node.js
2020-01-07 10:03:59 +00:00
]
} ,
2020-09-17 14:20:23 +00:00
// Common: vs/platform/native/common/native.ts
{
target : '**/vs/platform/native/common/native.ts' ,
allowedTypes : CORE_TYPES ,
2022-02-08 19:09:00 +00:00
disallowedTypes : [ /* Ignore native types that are defined from here */ ] ,
2020-09-17 14:20:23 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
'@types/node' // no node.js
]
} ,
2023-11-06 11:00:24 +00:00
// Common: vs/platform/native/common/nativeHostService.ts
{
target : '**/vs/platform/native/common/nativeHostService.ts' ,
allowedTypes : CORE_TYPES ,
disallowedTypes : [ /* Ignore native types that are defined from here */ ] ,
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
'@types/node' // no node.js
]
} ,
2020-01-07 10:03:59 +00:00
// Common: vs/workbench/api/common/extHostExtensionService.ts
{
target : '**/vs/workbench/api/common/extHostExtensionService.ts' ,
allowedTypes : [
. . . CORE_TYPES ,
// Safe access to global
'global'
] ,
2020-09-04 08:32:00 +00:00
disallowedTypes : NATIVE_TYPES ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
2020-01-07 10:10:01 +00:00
'@types/node' // no node.js
2020-01-07 10:03:59 +00:00
]
} ,
// Common
{
target : '**/vs/**/common/**' ,
allowedTypes : CORE_TYPES ,
2020-09-04 08:32:00 +00:00
disallowedTypes : NATIVE_TYPES ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' , // no DOM
2020-01-07 10:10:01 +00:00
'@types/node' // no node.js
2020-01-07 10:03:59 +00:00
]
} ,
// Browser
{
target : '**/vs/**/browser/**' ,
allowedTypes : CORE_TYPES ,
2020-09-04 08:32:00 +00:00
disallowedTypes : NATIVE_TYPES ,
2022-03-10 15:51:37 +00:00
allowedDefinitions : [
'@types/node/stream/consumers.d.ts' // node.js started to duplicate types from lib.dom.d.ts so we have to account for that
] ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
2020-01-07 10:10:01 +00:00
'@types/node' // no node.js
2020-01-07 10:03:59 +00:00
]
} ,
2020-02-05 10:18:53 +00:00
// Browser (editor contrib)
{
target : '**/src/vs/editor/contrib/**' ,
allowedTypes : CORE_TYPES ,
2020-09-04 08:32:00 +00:00
disallowedTypes : NATIVE_TYPES ,
2020-02-05 10:18:53 +00:00
disallowedDefinitions : [
'@types/node' // no node.js
]
} ,
2020-01-07 10:03:59 +00:00
// node.js
{
target : '**/vs/**/node/**' ,
2022-04-04 13:04:29 +00:00
allowedTypes : CORE_TYPES ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' // no DOM
]
} ,
2020-05-22 10:21:25 +00:00
// Electron (sandbox)
{
target : '**/vs/**/electron-sandbox/**' ,
allowedTypes : CORE_TYPES ,
disallowedDefinitions : [
'@types/node' // no node.js
]
} ,
2020-01-07 10:03:59 +00:00
// Electron (main)
{
target : '**/vs/**/electron-main/**' ,
allowedTypes : [
. . . CORE_TYPES ,
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
'Event' ,
'Request'
] ,
2022-04-12 05:46:17 +00:00
disallowedTypes : [
'ipcMain' // not allowed, use validatedIpcMain instead
] ,
2020-01-07 10:03:59 +00:00
disallowedDefinitions : [
'lib.dom.d.ts' // no DOM
]
}
] ;
const TS_CONFIG_PATH = join ( __dirname , '../../' , 'src' , 'tsconfig.json' ) ;
interface IRule {
target : string ;
skip? : boolean ;
allowedTypes? : string [ ] ;
2022-02-08 19:09:00 +00:00
allowedDefinitions? : string [ ] ;
2020-01-07 10:03:59 +00:00
disallowedDefinitions? : string [ ] ;
2020-09-04 08:32:00 +00:00
disallowedTypes? : string [ ] ;
2020-01-07 10:03:59 +00:00
}
let hasErrors = false ;
function checkFile ( program : ts.Program , sourceFile : ts.SourceFile , rule : IRule ) {
checkNode ( sourceFile ) ;
function checkNode ( node : ts.Node ) : void {
if ( node . kind !== ts . SyntaxKind . Identifier ) {
return ts . forEachChild ( node , checkNode ) ; // recurse down
}
2022-04-04 13:04:29 +00:00
const checker = program . getTypeChecker ( ) ;
const symbol = checker . getSymbolAtLocation ( node ) ;
if ( ! symbol ) {
return ;
}
let _parentSymbol : any = symbol ;
while ( _parentSymbol . parent ) {
_parentSymbol = _parentSymbol . parent ;
}
const parentSymbol = _parentSymbol as ts . Symbol ;
const text = parentSymbol . getName ( ) ;
2020-01-07 10:03:59 +00:00
if ( rule . allowedTypes ? . some ( allowed = > allowed === text ) ) {
return ; // override
}
2020-09-04 08:32:00 +00:00
if ( rule . disallowedTypes ? . some ( disallowed = > disallowed === text ) ) {
const { line , character } = sourceFile . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
2022-08-10 13:37:21 +00:00
console . log ( ` [build/lib/layersChecker.ts]: Reference to type ' ${ text } ' violates layer ' ${ rule . target } ' ( ${ sourceFile . fileName } ( ${ line + 1 } , ${ character + 1 } ). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization. ` ) ;
2020-09-04 08:32:00 +00:00
hasErrors = true ;
return ;
}
2022-04-04 13:04:29 +00:00
const declarations = symbol . declarations ;
if ( Array . isArray ( declarations ) ) {
DeclarationLoop : 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 . allowedDefinitions ) {
for ( const allowedDefinition of rule . allowedDefinitions ) {
if ( definitionFileName . indexOf ( allowedDefinition ) >= 0 ) {
continue DeclarationLoop ;
2022-02-08 19:09:00 +00:00
}
}
2022-04-04 13:04:29 +00:00
}
if ( rule . disallowedDefinitions ) {
for ( const disallowedDefinition of rule . disallowedDefinitions ) {
if ( definitionFileName . indexOf ( disallowedDefinition ) >= 0 ) {
const { line , character } = sourceFile . getLineAndCharacterOfPosition ( node . getStart ( ) ) ;
2022-08-10 13:37:21 +00:00
console . log ( ` [build/lib/layersChecker.ts]: Reference to symbol ' ${ text } ' from ' ${ disallowedDefinition } ' violates layer ' ${ rule . target } ' ( ${ sourceFile . fileName } ( ${ line + 1 } , ${ character + 1 } ) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization. ` ) ;
2022-04-04 13:04:29 +00:00
hasErrors = true ;
return ;
2020-01-07 10:10:01 +00:00
}
2020-01-07 10:03:59 +00:00
}
}
}
}
}
}
}
}
}
function createProgram ( tsconfigPath : string ) : ts . Program {
const tsConfig = ts . readConfigFile ( tsconfigPath , ts . sys . readFile ) ;
const configHostParser : ts.ParseConfigHost = { fileExists : existsSync , readDirectory : ts.sys.readDirectory , readFile : file = > readFileSync ( file , 'utf8' ) , useCaseSensitiveFileNames : process.platform === 'linux' } ;
const tsConfigParsed = ts . parseJsonConfigFileContent ( tsConfig . config , configHostParser , resolve ( 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 ( match ( [ sourceFile . fileName ] , rule . target ) . length > 0 ) {
if ( ! rule . skip ) {
checkFile ( program , sourceFile , rule ) ;
}
break ;
}
}
}
if ( hasErrors ) {
process . exit ( 1 ) ;
}