packages/aws-cdk-lib/scripts/verify-imports-resolve-same.ts (103 lines of code) (raw):

/* eslint-disable no-console */ /** * Verify that the two styles of imports we support: * * import { aws_ec2 } from 'aws-cdk-lib'; * import * as aws_ec2 from 'aws-cdk-lib/aws-ec2'; * * Resolve to the same source file when analyzed using the TypeScript compiler. * * This is necessary for Rosetta's analysis and translation of examples: we need * to know what submodule we're importing here, and we need to be able to deal * with both styles since both are used interchangeably. */ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs-extra'; // eslint-disable-next-line import/no-extraneous-dependencies import * as ts from 'typescript'; async function main() { // First make a tempdir and symlink `aws-cdk-lib` into it so we can refer to it // as if it was an installed module. await withTemporaryDirectory(async (tmpDir) => { await fs.mkdirp(path.join(tmpDir, 'node_modules')); await fs.symlink(path.resolve(__dirname, '..'), path.join(tmpDir, 'node_modules', 'aws-cdk-lib')); const import1 = 'import { aws_ec2 } from "aws-cdk-lib";'; const import2 = 'import * as aws_ec2 from "aws-cdk-lib/aws-ec2";'; const src1 = await compileAndResolve(path.join(tmpDir, 'program1.ts'), import1, 'aws_ec2'); const src2 = await compileAndResolve(path.join(tmpDir, 'program2.ts'), import2, 'aws_ec2'); if (src1 !== src2) { console.error('Import mismatch!'); console.error('\n ', import1, '\n'); console.error('resolves to', src1); console.error('\n ', import2, '\n'); console.error('resolves to', src2); process.exitCode = 1; } }); } async function compileAndResolve(fileName: string, contents: string, symbolName: string) { await fs.writeFile(fileName, contents + `\n\nconsole.log(${symbolName});`, { encoding: 'utf-8' }); const program = ts.createProgram({ rootNames: [fileName], options: STANDARD_COMPILER_OPTIONS }); const sourceFile = program.getSourceFile(fileName); if (!sourceFile) { throw new Error(`Could not find sourcefile back: ${fileName}`); } const diags = [ ...program.getGlobalDiagnostics(), ...program.getDeclarationDiagnostics(sourceFile), ...program.getSyntacticDiagnostics(sourceFile), ...program.getSemanticDiagnostics(sourceFile), ]; if (diags.length > 0) { console.error(ts.formatDiagnostics(diags, { getNewLine: () => '\n', getCurrentDirectory: () => path.dirname(fileName), getCanonicalFileName: (f) => path.resolve(f), })); throw new Error('Compilation failed'); } // Find the 'console.log()' back and resolve the symbol inside const logStmt = assertNode(sourceFile.statements[1], ts.isExpressionStatement); const logCall = assertNode(logStmt.expression, ts.isCallExpression); const ident = assertNode(logCall.arguments[0], ts.isIdentifier); let sym = program.getTypeChecker().getSymbolAtLocation(ident); // Resolve alias if applicable // eslint-disable-next-line no-bitwise while (sym && ((sym.flags & ts.SymbolFlags.Alias) !== 0)) { sym = program.getTypeChecker().getAliasedSymbol(sym); } if (!sym) { throw new Error(`Could not resolve: ${symbolName} in '${contents}'`); } // Return the filename const srcFile = sym.declarations?.[0].getSourceFile().fileName.replace(/[.](ts|js|d\.ts)$/, ''); if (!srcFile) { console.log(sym); throw new Error(`Symbol ${symbolName} in '${contents}' does not resolve to a source location`); } return srcFile; } export async function withTemporaryDirectory<T>(callback: (dir: string) => Promise<T>): Promise<T> { const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), path.basename(__filename))); try { return await callback(tmpdir); } finally { await fs.remove(tmpdir); } } function assertNode<A extends ts.Node>(x: ts.Node, assert: (x: ts.Node) => x is A): A { if (!assert(x)) { throw new Error(`Not the right type of node, expecting ${assert.name}, got ${ts.SyntaxKind[x.kind]}`); } return x; } export const STANDARD_COMPILER_OPTIONS: ts.CompilerOptions = { alwaysStrict: true, charset: 'utf8', declaration: true, experimentalDecorators: true, inlineSourceMap: true, inlineSources: true, lib: ['lib.es2016.d.ts', 'lib.es2017.object.d.ts', 'lib.es2017.string.d.ts'], module: ts.ModuleKind.CommonJS, noEmitOnError: true, noFallthroughCasesInSwitch: true, noImplicitAny: true, noImplicitReturns: true, noImplicitThis: true, noUnusedLocals: false, // Important, becomes super annoying without this noUnusedParameters: false, // Important, becomes super annoying without this resolveJsonModule: true, strict: true, strictNullChecks: true, strictPropertyInitialization: true, stripInternal: true, target: ts.ScriptTarget.ES2019, // Incremental builds incremental: true, tsBuildInfoFile: '.tsbuildinfo', }; main().catch((e) => { // eslint-disable-next-line no-console console.error(e); process.exitCode = 1; });