diff options
Diffstat (limited to 'tools/ts_library_builder')
-rw-r--r-- | tools/ts_library_builder/README.md | 88 | ||||
-rw-r--r-- | tools/ts_library_builder/ast_util.ts | 499 | ||||
-rw-r--r-- | tools/ts_library_builder/build_library.ts | 569 | ||||
-rw-r--r-- | tools/ts_library_builder/main.ts | 52 | ||||
-rw-r--r-- | tools/ts_library_builder/test.ts | 240 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/api.ts | 4 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/globals.ts | 15 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/lib.extra.d.ts | 7 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleA.ts | 9 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleB.ts | 9 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleC.ts | 18 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleD.ts | 5 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleE.ts | 5 | ||||
-rw-r--r-- | tools/ts_library_builder/testdata/moduleF.ts | 1 | ||||
-rw-r--r-- | tools/ts_library_builder/tsconfig.json | 9 |
15 files changed, 0 insertions, 1530 deletions
diff --git a/tools/ts_library_builder/README.md b/tools/ts_library_builder/README.md deleted file mode 100644 index 2eca169ae..000000000 --- a/tools/ts_library_builder/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# ts_library_builder - -This tool allows us to produce a single TypeScript declaration file that -describes the complete Deno runtime, including global variables and the built-in -`Deno` global object. The output of this tool, `lib.deno_runtime.d.ts`, serves -several purposes: - -1. It is passed to the TypeScript compiler `js/compiler.ts`, so that TypeScript - knows what types to expect and can validate code against the runtime - environment. -2. It is outputted to stdout by `deno types`, so that users can easily have - access to the complete declaration file. Editors can use this in the future - to perform type checking. -3. Because JSDocs are maintained, this serves as a simple documentation page for - Deno. We will use this file to generate HTML docs in the future. - -The tool depends upon a couple libraries: - -- [`ts-node`](https://www.npmjs.com/package/ts-node) to provide just in time - transpiling of TypeScript for the tool itself. -- [`ts-morph`](https://www.npmjs.com/package/ts-morph) which provides a more - rational and functional interface to the TypeScript AST to make manipulations - easier. -- [`prettier`](https://www.npmjs.com/package/prettier) and - [`@types/prettier`](https://www.npmjs.com/package/@types/prettier) to format - the output. - -## Design - -Ideally we wouldn't have to build this tool at all, and could simply use `tsc` -to output this declaration file. While, `--emitDeclarationsOnly`, `--outFile` -and `--module AMD` generates a single declaration file, it isn't clean. It was -never designed for a library generation, where what is available in a runtime -environment significantly differs from the code that creates that environment's -structure. - -Therefore this tool injects some of the knowledge of what occurs in the Deno -runtime environment as well as ensures that the output file is more clean and -logical for an end user. In the deno runtime, code runs in a global scope that -is defined in `js/global.ts`. This contains global scope items that one -reasonably expects in a JavaScript runtime, like `console`. It also defines the -global scope on a self-reflective `window` variable. There is currently only one -module of Deno specific APIs which is available to the user. This is defined in -`js/deno.ts`. - -This tool takes advantage of an experimental feature of TypeScript that items -that are not really intended to be part of the public API are marked with a -comment pragma of `@internal` and then are not emitted when generating type -definitions. In addition TypeScript will _tree-shake_ any dependencies tied to -that "hidden" API and elide them as well. This really helps keep the public API -clean and as minimal as needed. - -In order to create the default type library, the process at a high-level looks -like this: - -- We read in all of the runtime environment definition code into TypeScript AST - parser "project". -- We emit the TypeScript type definitions only into another AST parser - "project". -- We process the `deno` namespace/module, by "flattening" the type definition - file. - - We determine the exported symbols for `js/deno.ts`. - - We recurse over all imports/exports of the modules, only exporting those - symbols which are finally exported by `js/deno.ts`. - - We replace the import/export with the type information from the source file. - - This process assumes that all the modules that feed `js/deno.ts` will have a - public type API that does not have name conflicts. -- We process the `js/globals.ts` file to generate the global namespace. - - We create a `Window` interface and a `global` scope augmentation namespace. - - We iterate over augmentations to the `window` variable declared in the file, - extract the type information and apply it to both a global variable - declaration and a property on the `Window` interface. - - We identify any type aliases in the module and declare them globally. -- We take each namespace import to `js/globals.ts`, we resolve the emitted - declaration `.d.ts` file and create it as its own namespace within the global - scope. It is unsafe to just flatten these, because there is a high risk of - collisions, but also, it makes authoring the types easier within the generated - interface and variable declarations. -- We then validate the resulting definition file and write it out to the - appropriate build path. - -## TODO - -- The tool does not _tree-shake_ when flattening imports. This means there are - extraneous types that get included that are not really needed and it means - that `gen/msg_generated.ts` has to be explicitly carved down. -- Complete the tests... we have some coverage, but not a lot of what is in - `ast_util_test` which is being tested implicitly. diff --git a/tools/ts_library_builder/ast_util.ts b/tools/ts_library_builder/ast_util.ts deleted file mode 100644 index 142b26c00..000000000 --- a/tools/ts_library_builder/ast_util.ts +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import { basename, dirname, join, relative } from "path"; -import { readFileSync } from "fs"; -import { EOL } from "os"; -import { - ExportDeclaration, - ImportDeclaration, - InterfaceDeclaration, - JSDoc, - Project, - PropertySignature, - SourceFile, - StatementedNode, - ts, - TypeAliasDeclaration, - TypeGuards, - VariableStatement, - VariableDeclarationKind -} from "ts-morph"; - -let silent = false; - -/** Logs a message to the console. */ -export function log(message: any = "", ...args: any[]): void { - if (!silent) { - console.log(message, ...args); - } -} - -/** Sets the silent flag which impacts logging to the console. */ -export function setSilent(value = false): void { - silent = value; -} - -/** Add a property to an interface */ -export function addInterfaceProperty( - interfaceDeclaration: InterfaceDeclaration, - name: string, - type: string, - jsdocs?: JSDoc[] -): PropertySignature { - return interfaceDeclaration.addProperty({ - name, - type, - docs: jsdocs && jsdocs.map(jsdoc => jsdoc.getText()) - }); -} - -/** Add `@url` comment to node. */ -export function addSourceComment( - node: StatementedNode, - sourceFile: SourceFile, - rootPath: string -): void { - node.insertStatements( - 0, - `// @url ${relative(rootPath, sourceFile.getFilePath())}\n\n` - ); -} - -/** Add a declaration of a type alias to a node */ -export function addTypeAlias( - node: StatementedNode, - name: string, - type: string, - hasDeclareKeyword = false, - jsdocs?: JSDoc[] -): TypeAliasDeclaration { - return node.addTypeAlias({ - name, - type, - docs: jsdocs && jsdocs.map(jsdoc => jsdoc.getText()), - hasDeclareKeyword - }); -} - -/** Add a declaration of an interface to a node */ -export function addInterfaceDeclaration( - node: StatementedNode, - interfaceDeclaration: InterfaceDeclaration -) { - const interfaceStructure = interfaceDeclaration.getStructure(); - - return node.addInterface({ - name: interfaceStructure.name, - properties: interfaceStructure.properties, - docs: interfaceStructure.docs, - hasDeclareKeyword: true - }); -} - -/** Add a declaration of a variable to a node */ -export function addVariableDeclaration( - node: StatementedNode, - name: string, - type: string, - isConst: boolean, - hasDeclareKeyword?: boolean, - jsdocs?: JSDoc[] -): VariableStatement { - return node.addVariableStatement({ - declarationKind: isConst - ? VariableDeclarationKind.Const - : VariableDeclarationKind.Let, - declarations: [{ name, type }], - docs: jsdocs && jsdocs.map(jsdoc => jsdoc.getText()), - hasDeclareKeyword - }); -} - -/** Copy one source file to the end of another source file. */ -export function appendSourceFile( - sourceFile: SourceFile, - targetSourceFile: SourceFile -): void { - targetSourceFile.addStatements(`\n${sourceFile.print()}`); -} - -/** Used when formatting diagnostics */ -const formatDiagnosticHost: ts.FormatDiagnosticsHost = { - getCurrentDirectory() { - return process.cwd(); - }, - getCanonicalFileName(path: string) { - return path; - }, - getNewLine() { - return EOL; - } -}; - -/** Log diagnostics to the console with colour. */ -export function logDiagnostics(diagnostics: ts.Diagnostic[]): void { - if (diagnostics.length) { - console.log( - ts.formatDiagnosticsWithColorAndContext(diagnostics, formatDiagnosticHost) - ); - } -} - -/** Check diagnostics, and if any exist, exit the process */ -export function checkDiagnostics(project: Project, onlyFor?: string[]): void { - const program = project.getProgram(); - const diagnostics = [ - ...program.getGlobalDiagnostics(), - ...program.getSyntacticDiagnostics(), - ...program.getSemanticDiagnostics(), - ...program.getDeclarationDiagnostics() - ] - .filter(diagnostic => { - const sourceFile = diagnostic.getSourceFile(); - return onlyFor && sourceFile - ? onlyFor.includes(sourceFile.getFilePath()) - : true; - }) - .map(diagnostic => diagnostic.compilerObject); - - logDiagnostics(diagnostics); - - if (diagnostics.length) { - process.exit(1); - } -} - -function createDeclarationError( - msg: string, - declaration: ImportDeclaration | ExportDeclaration -): Error { - return new Error( - `${msg}\n` + - ` In: "${declaration.getSourceFile().getFilePath()}"\n` + - ` Text: "${declaration.getText()}"` - ); -} - -export interface FlattenNamespaceOptions { - customSources?: { [sourceFilePath: string]: string }; - debug?: boolean; - rootPath: string; - sourceFile: SourceFile; -} - -/** Returns a string which indicates the source file as the source */ -export function getSourceComment( - sourceFile: SourceFile, - rootPath: string -): string { - return `\n// @url ${relative(rootPath, sourceFile.getFilePath())}\n\n`; -} - -/** Return a set of fully qualified symbol names for the files exports */ -function getExportedSymbols(sourceFile: SourceFile): Set<string> { - const exportedSymbols = new Set<string>(); - const exportDeclarations = sourceFile.getExportDeclarations(); - for (const exportDeclaration of exportDeclarations) { - const exportSpecifiers = exportDeclaration.getNamedExports(); - for (const exportSpecifier of exportSpecifiers) { - const aliasedSymbol = exportSpecifier - .getSymbolOrThrow() - .getAliasedSymbol(); - if (aliasedSymbol) { - exportedSymbols.add(aliasedSymbol.getFullyQualifiedName()); - } - } - } - return exportedSymbols; -} - -/** Take a namespace and flatten all exports. */ -export function flattenNamespace({ - customSources, - debug, - rootPath, - sourceFile -}: FlattenNamespaceOptions): string { - const sourceFiles = new Set<SourceFile>(); - let output = ""; - const exportedSymbols = getExportedSymbols(sourceFile); - - function flattenDeclarations( - declaration: ImportDeclaration | ExportDeclaration - ): void { - const declarationSourceFile = declaration.getModuleSpecifierSourceFile(); - if (declarationSourceFile) { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - processSourceFile(declarationSourceFile); - declaration.remove(); - } - } - - function rectifyNodes(currentSourceFile: SourceFile): void { - currentSourceFile.forEachChild(node => { - if (TypeGuards.isAmbientableNode(node)) { - node.setHasDeclareKeyword(false); - } - if (TypeGuards.isExportableNode(node)) { - const nodeSymbol = node.getSymbol(); - if ( - nodeSymbol && - !exportedSymbols.has(nodeSymbol.getFullyQualifiedName()) - ) { - node.setIsExported(false); - } - } - }); - } - - function processSourceFile( - currentSourceFile: SourceFile - ): string | undefined { - if (sourceFiles.has(currentSourceFile)) { - return; - } - sourceFiles.add(currentSourceFile); - - const currentSourceFilePath = currentSourceFile - .getFilePath() - .replace(/(\.d)?\.ts$/, ""); - log("Process source file:", currentSourceFilePath); - if (customSources && currentSourceFilePath in customSources) { - log(" Using custom source."); - output += customSources[currentSourceFilePath]; - return; - } - - currentSourceFile.getImportDeclarations().forEach(flattenDeclarations); - currentSourceFile.getExportDeclarations().forEach(flattenDeclarations); - - rectifyNodes(currentSourceFile); - - output += - (debug ? getSourceComment(currentSourceFile, rootPath) : "") + - currentSourceFile.print(); - } - - sourceFile.getExportDeclarations().forEach(exportDeclaration => { - const exportedSourceFile = exportDeclaration.getModuleSpecifierSourceFile(); - if (exportedSourceFile) { - processSourceFile(exportedSourceFile); - } else { - throw createDeclarationError("Missing source file.", exportDeclaration); - } - exportDeclaration.remove(); - }); - - rectifyNodes(sourceFile); - - return ( - output + - (debug ? getSourceComment(sourceFile, rootPath) : "") + - sourceFile.print() - ); -} - -interface InlineFilesOptions { - basePath: string; - debug?: boolean; - inline: string[]; - targetSourceFile: SourceFile; -} - -/** Inline files into the target source file. */ -export function inlineFiles({ - basePath, - debug, - inline, - targetSourceFile -}: InlineFilesOptions): void { - for (const filename of inline) { - const text = readFileSync(filename, { - encoding: "utf8" - }); - targetSourceFile.addStatements( - debug - ? `\n// @url ${relative(basePath, filename)}\n\n${text}` - : `\n${text}` - ); - } -} - -/** Load a set of files into a file system host. */ -export function loadFiles( - project: Project, - filePaths: string[], - rebase?: string -): void { - const fileSystem = project.getFileSystem(); - for (const filePath of filePaths) { - const fileText = readFileSync(filePath, { - encoding: "utf8" - }); - fileSystem.writeFileSync( - rebase ? join(rebase, basename(filePath)) : filePath, - fileText - ); - } -} - -/** - * Load and write to a virtual file system all the default libs needed to - * resolve types on project. - */ -export function loadDtsFiles( - project: Project, - compilerOptions: ts.CompilerOptions -): void { - const libSourcePath = dirname(ts.getDefaultLibFilePath(compilerOptions)); - // TODO (@kitsonk) Add missing libs when ts-morph supports TypeScript 3.4 - loadFiles( - project, - [ - "lib.es2015.collection.d.ts", - "lib.es2015.core.d.ts", - "lib.es2015.d.ts", - "lib.es2015.generator.d.ts", - "lib.es2015.iterable.d.ts", - "lib.es2015.promise.d.ts", - "lib.es2015.proxy.d.ts", - "lib.es2015.reflect.d.ts", - "lib.es2015.symbol.d.ts", - "lib.es2015.symbol.wellknown.d.ts", - "lib.es2016.array.include.d.ts", - "lib.es2016.d.ts", - "lib.es2017.d.ts", - "lib.es2017.intl.d.ts", - "lib.es2017.object.d.ts", - "lib.es2017.sharedmemory.d.ts", - "lib.es2017.string.d.ts", - "lib.es2017.typedarrays.d.ts", - "lib.es2018.d.ts", - "lib.es2018.intl.d.ts", - "lib.es2018.promise.d.ts", - "lib.es5.d.ts", - "lib.esnext.d.ts", - "lib.esnext.array.d.ts", - "lib.esnext.asynciterable.d.ts", - "lib.esnext.intl.d.ts", - "lib.esnext.symbol.d.ts" - ].map(fileName => join(libSourcePath, fileName)), - "node_modules/typescript/lib/" - ); -} - -export interface NamespaceSourceFileOptions { - debug?: boolean; - namespace?: string; - namespaces: Set<string>; - rootPath: string; - sourceFileMap: Map<SourceFile, string>; -} - -/** - * Take a source file (`.d.ts`) and convert it to a namespace, resolving any - * imports as their own namespaces. - */ -export function namespaceSourceFile( - sourceFile: SourceFile, - { - debug, - namespace, - namespaces, - rootPath, - sourceFileMap - }: NamespaceSourceFileOptions -): string { - if (sourceFileMap.has(sourceFile)) { - return ""; - } - if (!namespace) { - namespace = sourceFile.getBaseNameWithoutExtension(); - } - sourceFileMap.set(sourceFile, namespace); - - sourceFile.forEachChild(node => { - if (TypeGuards.isAmbientableNode(node)) { - node.setHasDeclareKeyword(false); - } - }); - - // TODO need to properly unwrap this - const globalNamespace = sourceFile.getNamespace("global"); - let globalNamespaceText = ""; - if (globalNamespace) { - const structure = globalNamespace.getStructure(); - if (structure.bodyText && typeof structure.bodyText === "string") { - globalNamespaceText = structure.bodyText; - } else { - throw new TypeError("Unexpected global declaration structure."); - } - } - if (globalNamespace) { - globalNamespace.remove(); - } - - const output = sourceFile - .getImportDeclarations() - .filter(declaration => { - const dsf = declaration.getModuleSpecifierSourceFile(); - if (dsf == null) { - try { - const namespaceName = declaration - .getNamespaceImportOrThrow() - .getText(); - if (!namespaces.has(namespaceName)) { - throw createDeclarationError( - "Already defined source file under different namespace.", - declaration - ); - } - } catch (e) { - throw createDeclarationError( - `Unsupported import clause: ${e}`, - declaration - ); - } - declaration.remove(); - } - return dsf; - }) - .map(declaration => { - if ( - declaration.getNamedImports().length || - !declaration.getNamespaceImport() - ) { - throw createDeclarationError("Unsupported import clause.", declaration); - } - const text = namespaceSourceFile( - declaration.getModuleSpecifierSourceFileOrThrow(), - { - debug, - namespace: declaration.getNamespaceImportOrThrow().getText(), - namespaces, - rootPath, - sourceFileMap - } - ); - declaration.remove(); - return text; - }) - .join("\n"); - sourceFile - .getExportDeclarations() - .forEach(declaration => declaration.remove()); - - namespaces.add(namespace); - - return `${output} - ${globalNamespaceText || ""} - - declare namespace ${namespace} { - ${debug ? getSourceComment(sourceFile, rootPath) : ""} - ${sourceFile.getText()} - }`; -} - -/** Mirrors TypeScript's handling of paths */ -export function normalizeSlashes(path: string): string { - return path.replace(/\\/g, "/"); -} diff --git a/tools/ts_library_builder/build_library.ts b/tools/ts_library_builder/build_library.ts deleted file mode 100644 index 0a18abcbb..000000000 --- a/tools/ts_library_builder/build_library.ts +++ /dev/null @@ -1,569 +0,0 @@ -import { writeFileSync } from "fs"; -import { join } from "path"; -import * as prettier from "prettier"; -import { - InterfaceDeclaration, - ExpressionStatement, - NamespaceDeclarationKind, - Project, - SourceFile, - ts, - Type, - TypeGuards -} from "ts-morph"; -import { - addInterfaceDeclaration, - addInterfaceProperty, - addSourceComment, - addTypeAlias, - addVariableDeclaration, - checkDiagnostics, - flattenNamespace, - getSourceComment, - inlineFiles, - loadDtsFiles, - loadFiles, - log, - logDiagnostics, - namespaceSourceFile, - normalizeSlashes, - setSilent -} from "./ast_util"; - -export interface BuildLibraryOptions { - /** - * The path to the root of the deno repository - */ - basePath: string; - - /** - * The path to the current build path - */ - buildPath: string; - - /** - * Denotes if the library should be built with debug information (comments - * that indicate the source of the types) - */ - debug?: boolean; - - /** - * An array of files that should be inlined into the library - */ - inline?: string[]; - - /** An array of input files to be provided to the input project, relative to - * the basePath. */ - inputs?: string[]; - - /** - * Path to globals file to be used I.E. `js/globals.ts` - */ - additionalGlobals?: string[]; - - /** - * List of global variables to define as let instead of the default const. - */ - declareAsLet?: string[]; - - /** - * The path to the output library - */ - outFile: string; - - /** - * Execute in silent mode or not - */ - silent?: boolean; -} - -const { ModuleKind, ModuleResolutionKind, ScriptTarget } = ts; - -/** - * A preamble which is appended to the start of the library. - */ -const libPreamble = `// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -/// <reference no-default-lib="true" /> -/// <reference lib="esnext" /> - -`; - -interface FlattenOptions { - basePath: string; - customSources: { [filePath: string]: string }; - filePath: string; - debug?: boolean; - declarationProject: Project; - globalInterfaceName?: string; - moduleName?: string; - namespaceName?: string; - targetSourceFile: SourceFile; -} - -/** Flatten a module */ -export function flatten({ - basePath, - customSources, - filePath, - debug, - declarationProject, - globalInterfaceName, - moduleName, - namespaceName, - targetSourceFile -}: FlattenOptions): void { - // Flatten the source file into a single set of statements - const statements = flattenNamespace({ - sourceFile: declarationProject.getSourceFileOrThrow(filePath), - rootPath: basePath, - customSources, - debug - }); - - // If a module name is specified create the module in the target file - if (moduleName) { - const namespace = targetSourceFile.addNamespace({ - name: moduleName, - hasDeclareKeyword: true, - declarationKind: NamespaceDeclarationKind.Module - }); - - // Add the output of the flattening to the namespace - namespace.addStatements(statements); - } - - if (namespaceName) { - const namespace = targetSourceFile.insertNamespace(0, { - name: namespaceName, - hasDeclareKeyword: true, - declarationKind: NamespaceDeclarationKind.Namespace - }); - - // Add the output of the flattening to the namespace - namespace.addStatements(statements); - - if (globalInterfaceName) { - // Retrieve the global interface - const interfaceDeclaration = targetSourceFile.getInterfaceOrThrow( - globalInterfaceName - ); - - // Add the namespace to the global interface - addInterfaceProperty( - interfaceDeclaration, - namespaceName, - `typeof ${namespaceName}` - ); - } - } -} - -interface PrepareFileForMergeOptions { - globalVarName: string; - interfaceName: string; - targetSourceFile: SourceFile; -} - -interface PrepareFileForMergeReturn { - interfaceDeclaration: InterfaceDeclaration; -} - -export function prepareFileForMerge({ - globalVarName, - interfaceName, - targetSourceFile -}: PrepareFileForMergeOptions): PrepareFileForMergeReturn { - // Add the global object interface - const interfaceDeclaration = targetSourceFile.addInterface({ - name: interfaceName, - hasDeclareKeyword: true - }); - - // Declare the global variable - addVariableDeclaration( - targetSourceFile, - globalVarName, - interfaceName, - true, - true - ); - - // Add self reference to the global variable - addInterfaceProperty(interfaceDeclaration, globalVarName, interfaceName); - - return { - interfaceDeclaration - }; -} - -interface MergeGlobalOptions extends PrepareFileForMergeOptions { - basePath: string; - debug?: boolean; - declarationProject: Project; - filePath: string; - ignore?: string[]; - inputProject: Project; - prepareReturn: PrepareFileForMergeReturn; - declareAsLet?: string[]; -} - -/** Take a module and merge it into the global scope */ -export function mergeGlobals({ - basePath, - debug, - declarationProject, - filePath, - globalVarName, - ignore, - inputProject, - targetSourceFile, - declareAsLet, - prepareReturn: { interfaceDeclaration } -}: MergeGlobalOptions): void { - // Retrieve source file from the input project - const sourceFile = inputProject.getSourceFileOrThrow(filePath); - - // we are going to create a map of variables - const globalVariables = new Map< - string, - { - type: Type; - node: ExpressionStatement; - } - >(); - const globalInterfaces: InterfaceDeclaration[] = []; - - // For every augmentation of the global variable in source file, we want - // to extract the type and add it to the global variable map - sourceFile.forEachChild(node => { - if (TypeGuards.isExpressionStatement(node)) { - const firstChild = node.getFirstChild(); - if (!firstChild) { - return; - } - if (TypeGuards.isBinaryExpression(firstChild)) { - const leftExpression = firstChild.getLeft(); - if ( - TypeGuards.isPropertyAccessExpression(leftExpression) && - leftExpression.getExpression().getText() === globalVarName - ) { - const globalVarProperty = leftExpression.getName(); - if (globalVarProperty !== globalVarName) { - globalVariables.set(globalVarProperty, { - type: firstChild.getType(), - node - }); - } - } - } - } else if (TypeGuards.isInterfaceDeclaration(node) && node.isExported()) { - globalInterfaces.push(node); - } - }); - - // A set of source files that the types we are using are dependent on us - // importing - const dependentSourceFiles = new Set<SourceFile>(); - - // Create a global variable and add the property to the `Window` interface - // for each mutation of the `window` variable we observed in `globals.ts` - for (const [property, info] of globalVariables) { - if (!(ignore && ignore.includes(property))) { - const type = info.type.getText(info.node); - const typeSymbol = info.type.getSymbol(); - if (typeSymbol) { - const valueDeclaration = typeSymbol.getValueDeclaration(); - if (valueDeclaration) { - dependentSourceFiles.add(valueDeclaration.getSourceFile()); - } - } - const isConst = !(declareAsLet && declareAsLet.includes(property)); - addVariableDeclaration(targetSourceFile, property, type, isConst, true); - addInterfaceProperty(interfaceDeclaration, property, type); - } - } - - // We need to copy over any type aliases - for (const typeAlias of sourceFile.getTypeAliases()) { - addTypeAlias( - targetSourceFile, - typeAlias.getName(), - typeAlias.getType().getText(sourceFile), - true - ); - } - - // We need to copy over any interfaces - for (const interfaceDeclaration of globalInterfaces) { - addInterfaceDeclaration(targetSourceFile, interfaceDeclaration); - } - - // We need to ensure that we only namespace each source file once, so we - // will use this map for tracking that. - const sourceFileMap = new Map<SourceFile, string>(); - - // For each import declaration in source file we will want to convert the - // declaration source file into a namespace that exists within the merged - // namespace - const importDeclarations = sourceFile.getImportDeclarations(); - const namespaces = new Set<string>(); - for (const declaration of importDeclarations) { - const namespaceImport = declaration.getNamespaceImport(); - if (namespaceImport) { - const declarationSourceFile = declaration.getModuleSpecifierSourceFile(); - if ( - declarationSourceFile && - dependentSourceFiles.has(declarationSourceFile) - ) { - // the source file will resolve to the original `.ts` file, but the - // information we really want is in the emitted `.d.ts` file, so we will - // resolve to that file - const dtsFilePath = declarationSourceFile - .getFilePath() - .replace(/\.ts$/, ".d.ts"); - const dtsSourceFile = declarationProject.getSourceFileOrThrow( - dtsFilePath - ); - targetSourceFile.addStatements( - namespaceSourceFile(dtsSourceFile, { - debug, - namespace: namespaceImport.getText(), - namespaces, - rootPath: basePath, - sourceFileMap - }) - ); - } - } - } - - if (debug) { - addSourceComment(targetSourceFile, sourceFile, basePath); - } -} - -/** - * Generate the runtime library for Deno and write it to the supplied out file - * name. - */ -export function main({ - basePath, - buildPath, - inline, - inputs, - additionalGlobals, - declareAsLet, - debug, - outFile, - silent -}: BuildLibraryOptions): void { - setSilent(silent); - log("-----"); - log("build_lib"); - log(); - log(`basePath: "${basePath}"`); - log(`buildPath: "${buildPath}"`); - if (inline && inline.length) { - log("inline:"); - for (const filename of inline) { - log(` "${filename}"`); - } - } - if (inputs && inputs.length) { - log("inputs:"); - for (const input of inputs) { - log(` "${input}"`); - } - } - log(`debug: ${!!debug}`); - log(`outFile: "${outFile}"`); - log(); - - // the inputProject will take in the TypeScript files that are internal - // to Deno to be used to generate the library - const inputProject = new Project({ - compilerOptions: { - baseUrl: basePath, - declaration: true, - emitDeclarationOnly: true, - lib: ["esnext"], - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.NodeJs, - paths: { - "*": ["*", `${buildPath}/*`] - }, - preserveConstEnums: true, - strict: true, - stripInternal: true, - target: ScriptTarget.ESNext - } - }); - - // Add the input files we will need to generate the declarations, `globals` - // plus any modules that are importable in the runtime need to be added here - // plus the `lib.esnext` which is used as the base library - if (inputs) { - inputProject.addExistingSourceFiles( - inputs.map(input => join(basePath, input)) - ); - } - - // emit the project, which will be only the declaration files - const inputEmitResult = inputProject.emitToMemory(); - - log("Emitted input project."); - - const inputDiagnostics = inputEmitResult - .getDiagnostics() - .map(d => d.compilerObject); - logDiagnostics(inputDiagnostics); - if (inputDiagnostics.length) { - console.error("\nDiagnostics present during input project emit.\n"); - process.exit(1); - } - - // the declaration project will be the target for the emitted files from - // the input project, these will be used to transfer information over to - // the final library file - const declarationProject = new Project({ - compilerOptions: { - baseUrl: basePath, - lib: ["esnext"], - moduleResolution: ModuleResolutionKind.NodeJs, - paths: { - "*": ["*", `${buildPath}/*`] - }, - strict: true, - target: ScriptTarget.ESNext - }, - useVirtualFileSystem: true - }); - - // we don't want to add to the declaration project any of the original - // `.ts` source files, so we need to filter those out - const jsPath = normalizeSlashes(`${basePath}/js`); - const inputProjectFiles = inputProject - .getSourceFiles() - .map(sourceFile => sourceFile.getFilePath()) - .filter(filePath => !filePath.startsWith(jsPath)); - loadFiles(declarationProject, inputProjectFiles); - - // now we add the emitted declaration files from the input project - for (const { filePath, text } of inputEmitResult.getFiles()) { - declarationProject.createSourceFile(filePath, text); - } - - // the outputProject will contain the final library file we are looking to - // build - const outputProjectCompilerOptions: ts.CompilerOptions = { - baseUrl: buildPath, - lib: ["esnext"], - moduleResolution: ModuleResolutionKind.NodeJs, - strict: true, - target: ScriptTarget.ESNext - }; - - const outputProject = new Project({ - compilerOptions: outputProjectCompilerOptions, - useVirtualFileSystem: true - }); - - // There are files we need to load into memory, so that the project "compiles" - loadDtsFiles(outputProject, outputProjectCompilerOptions); - - // libDts is the final output file we are looking to build and we are not - // actually creating it, only in memory at this stage. - const libDTs = outputProject.createSourceFile(outFile); - - // Deal with `js/deno.ts` - - // Generate a object hash of substitutions of modules to use when flattening - const customSources = {}; - - const prepareForMergeOpts: PrepareFileForMergeOptions = { - globalVarName: "window", - interfaceName: "Window", - targetSourceFile: libDTs - }; - - const prepareReturn = prepareFileForMerge(prepareForMergeOpts); - - mergeGlobals({ - basePath, - debug, - declarationProject, - filePath: `${basePath}/js/globals.ts`, - inputProject, - ignore: ["Deno"], - declareAsLet, - ...prepareForMergeOpts, - prepareReturn - }); - - log(`Merged "globals" into global scope.`); - - if (additionalGlobals) { - for (const additionalGlobal of additionalGlobals) { - mergeGlobals({ - basePath, - debug, - declarationProject, - filePath: `${basePath}/${additionalGlobal}`, - inputProject, - ignore: ["Deno"], - declareAsLet, - ...prepareForMergeOpts, - prepareReturn - }); - } - - log(`Added additional "globals" into global scope.`); - } - - flatten({ - basePath, - customSources, - debug, - declarationProject, - filePath: `${basePath}/js/deno.d.ts`, - globalInterfaceName: "Window", - namespaceName: "Deno", - targetSourceFile: libDTs - }); - - log(`Created module "deno" and namespace Deno.`); - - // Inline any files that were passed in, to be used to add additional libs - // which are not part of TypeScript. - if (inline && inline.length) { - inlineFiles({ - basePath, - debug, - inline, - targetSourceFile: libDTs - }); - } - - // Add the preamble - libDTs.insertStatements(0, libPreamble); - - // Check diagnostics - checkDiagnostics(outputProject); - - // Output the final library file - libDTs.saveSync(); - const libDTsText = prettier.format( - outputProject.getFileSystem().readFileSync(outFile, "utf8"), - { parser: "typescript" } - ); - if (!silent) { - console.log(`Outputting library to: "${outFile}"`); - console.log(` Length: ${libDTsText.length}`); - } - writeFileSync(outFile, libDTsText, { encoding: "utf8" }); - if (!silent) { - console.log("-----"); - console.log(); - } -} diff --git a/tools/ts_library_builder/main.ts b/tools/ts_library_builder/main.ts deleted file mode 100644 index e4e2e73ed..000000000 --- a/tools/ts_library_builder/main.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import * as path from "path"; -import { main as buildRuntimeLib } from "./build_library"; - -// this is very simplistic argument parsing, just enough to integrate into -// the build scripts, versus being very robust -let basePath = process.cwd(); -let buildPath = path.join(basePath, "target", "debug"); -let outFile = path.join(buildPath, "gen", "lib", "lib.d.ts"); -let inline: string[] = []; -let debug = false; -let silent = false; - -process.argv.forEach((arg, i, argv) => { - switch (arg) { - case "--basePath": - basePath = path.resolve(argv[i + 1]); - break; - case "--buildPath": - buildPath = path.resolve(argv[i + 1]); - break; - case "--inline": - inline = argv[i + 1].split(",").map(filename => { - return path.resolve(filename); - }); - break; - case "--outFile": - outFile = path.resolve(argv[i + 1]); - break; - case "--debug": - debug = true; - break; - case "--silent": - silent = true; - break; - } -}); - -buildRuntimeLib({ - basePath, - buildPath, - debug, - inline, - inputs: [ - "node_modules/typescript/lib/lib.esnext.d.ts", - "js/deno.ts", - "js/globals.ts" - ], - declareAsLet: ["onmessage"], - outFile, - silent -}); diff --git a/tools/ts_library_builder/test.ts b/tools/ts_library_builder/test.ts deleted file mode 100644 index d5f9de646..000000000 --- a/tools/ts_library_builder/test.ts +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -// Run this manually with: -// -// ./node_modules/.bin/ts-node --project tools/ts_library_builder/tsconfig.json tools/ts_library_builder/test.ts - -import * as assert from "assert"; -import { Project, ts } from "ts-morph"; -import { flatten, mergeGlobals, prepareFileForMerge } from "./build_library"; -import { inlineFiles, loadDtsFiles } from "./ast_util"; - -const { ModuleKind, ModuleResolutionKind, ScriptTarget } = ts; - -/** setups and returns the fixtures for testing */ -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -function setupFixtures() { - const basePath = process.cwd(); - const buildPath = `${basePath}/tools/ts_library_builder/testdata`; - const outputFile = `${buildPath}/lib.output.d.ts`; - const inputProject = new Project({ - compilerOptions: { - baseUrl: basePath, - declaration: true, - emitDeclarationOnly: true, - module: ModuleKind.ESNext, - moduleResolution: ModuleResolutionKind.NodeJs, - strict: true, - stripInternal: true, - target: ScriptTarget.ESNext - } - }); - inputProject.addExistingSourceFiles([ - `${buildPath}/globals.ts`, - `${buildPath}/api.ts` - ]); - const declarationProject = new Project({ - compilerOptions: {}, - useVirtualFileSystem: true - }); - loadDtsFiles(declarationProject, {}); - for (const { filePath, text } of inputProject.emitToMemory().getFiles()) { - declarationProject.createSourceFile(filePath, text); - } - const outputProject = new Project({ - compilerOptions: {}, - useVirtualFileSystem: true - }); - loadDtsFiles(outputProject, {}); - const outputSourceFile = outputProject.createSourceFile(outputFile); - const debug = true; - - return { - basePath, - buildPath, - inputProject, - outputFile, - declarationProject, - outputProject, - outputSourceFile, - debug - }; -} - -function buildLibraryFlatten(): void { - const { - basePath, - buildPath, - debug, - declarationProject, - outputSourceFile: targetSourceFile - } = setupFixtures(); - - flatten({ - basePath, - customSources: {}, - debug, - declarationProject, - filePath: `${buildPath}/api.d.ts`, - moduleName: `"api"`, - namespaceName: "Api", - targetSourceFile - }); - - assert(targetSourceFile.getNamespace(`"api"`) != null); - assert(targetSourceFile.getNamespace("Api") != null); - assert.equal(targetSourceFile.getNamespaces().length, 2); - const moduleApi = targetSourceFile.getNamespaceOrThrow(`"api"`); - const functions = moduleApi.getFunctions(); - assert.equal(functions[0].getName(), "foo"); - assert.equal( - functions[0] - .getJsDocs() - .map(jsdoc => jsdoc.getInnerText()) - .join("\n"), - "jsdoc for foo" - ); - assert.equal(functions[1].getName(), "bar"); - assert.equal( - functions[1] - .getJsDocs() - .map(jsdoc => jsdoc.getInnerText()) - .join("\n"), - "" - ); - assert.equal(functions.length, 2); - const classes = moduleApi.getClasses(); - assert.equal(classes[0].getName(), "Foo"); - assert.equal(classes.length, 1); - const variableDeclarations = moduleApi.getVariableDeclarations(); - assert.equal(variableDeclarations[0].getName(), "arr"); - assert.equal(variableDeclarations.length, 1); - - const namespaceApi = targetSourceFile.getNamespaceOrThrow(`"api"`); - const functionsNs = namespaceApi.getFunctions(); - assert.equal(functionsNs[0].getName(), "foo"); - assert.equal( - functionsNs[0] - .getJsDocs() - .map(jsdoc => jsdoc.getInnerText()) - .join("\n"), - "jsdoc for foo" - ); - assert.equal(functionsNs[1].getName(), "bar"); - assert.equal( - functionsNs[1] - .getJsDocs() - .map(jsdoc => jsdoc.getInnerText()) - .join("\n"), - "" - ); - assert.equal(functionsNs.length, 2); - const classesNs = namespaceApi.getClasses(); - assert.equal(classesNs[0].getName(), "Foo"); - assert.equal(classesNs.length, 1); - const variableDeclarationsNs = namespaceApi.getVariableDeclarations(); - assert.equal(variableDeclarationsNs[0].getName(), "arr"); - assert.equal(variableDeclarationsNs.length, 1); -} - -function buildLibraryMerge(): void { - const { - basePath, - buildPath, - declarationProject, - debug, - inputProject, - outputSourceFile: targetSourceFile - } = setupFixtures(); - - const prepareForMergeOpts = { - globalVarName: "foobarbaz", - interfaceName: "FooBar", - targetSourceFile - }; - - const prepareReturn = prepareFileForMerge(prepareForMergeOpts); - - mergeGlobals({ - basePath, - declarationProject, - debug, - filePath: `${buildPath}/globals.ts`, - inputProject, - ...prepareForMergeOpts, - prepareReturn - }); - - assert(targetSourceFile.getNamespace("moduleC") != null); - assert(targetSourceFile.getNamespace("moduleD") != null); - assert(targetSourceFile.getNamespace("moduleE") != null); - assert(targetSourceFile.getNamespace("moduleF") != null); - assert.equal(targetSourceFile.getNamespaces().length, 4); - assert(targetSourceFile.getInterface("FooBar") != null); - assert.equal(targetSourceFile.getInterfaces().length, 2); - const variableDeclarations = targetSourceFile.getVariableDeclarations(); - assert.equal(variableDeclarations[0].getType().getText(), `FooBar`); - assert.equal(variableDeclarations[1].getType().getText(), `moduleC.Bar`); - assert.equal( - variableDeclarations[2].getType().getText(), - `typeof moduleC.qat` - ); - assert.equal( - variableDeclarations[3].getType().getText(), - `typeof moduleE.process` - ); - assert.equal( - variableDeclarations[4].getType().getText(), - `typeof moduleD.reprocess` - ); - assert.equal( - variableDeclarations[5].getType().getText(), - `typeof moduleC.Bar` - ); - assert.equal(variableDeclarations.length, 6); - const typeAliases = targetSourceFile.getTypeAliases(); - assert.equal(typeAliases[0].getName(), "Bar"); - assert.equal(typeAliases[0].getType().getText(), "moduleC.Bar"); - assert.equal(typeAliases.length, 1); - const exportedInterface = targetSourceFile.getInterfaceOrThrow("FizzBuzz"); - const interfaceProperties = exportedInterface.getStructure().properties; - assert(interfaceProperties != null); - assert.equal(interfaceProperties!.length, 2); - assert.equal(interfaceProperties![0].name, "foo"); - assert.equal(interfaceProperties![0].type, "string"); - assert.equal(interfaceProperties![1].name, "bar"); - assert.equal(interfaceProperties![1].type, "number"); -} - -function testInlineFiles(): void { - const { - basePath, - buildPath, - debug, - outputSourceFile: targetSourceFile - } = setupFixtures(); - - inlineFiles({ - basePath, - debug, - inline: [`${buildPath}/lib.extra.d.ts`], - targetSourceFile - }); - - assert(targetSourceFile.getNamespace("Qat") != null); - const qatNamespace = targetSourceFile.getNamespaceOrThrow("Qat"); - assert(qatNamespace.getClass("Foo") != null); -} - -// TODO author unit tests for `ast_util.ts` - -function main(): void { - console.log("ts_library_builder buildLibraryFlatten"); - buildLibraryFlatten(); - console.log("ts_library_builder buildLibraryMerge"); - buildLibraryMerge(); - console.log("ts_library_builder testInlineFiles"); - testInlineFiles(); - console.log("ts_library_builder ok"); -} - -main(); diff --git a/tools/ts_library_builder/testdata/api.ts b/tools/ts_library_builder/testdata/api.ts deleted file mode 100644 index f282a8414..000000000 --- a/tools/ts_library_builder/testdata/api.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { foo, bar } from "./moduleA"; -export { Foo } from "./moduleB"; -/** jsdoc for arr */ -export const arr: string[] = []; diff --git a/tools/ts_library_builder/testdata/globals.ts b/tools/ts_library_builder/testdata/globals.ts deleted file mode 100644 index 4fff7e8f9..000000000 --- a/tools/ts_library_builder/testdata/globals.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as moduleC from "./moduleC"; -import * as moduleD from "./moduleD"; -import * as moduleE from "./moduleE"; - -const foobarbaz: any = {}; -foobarbaz.bar = new moduleC.Bar(); -foobarbaz.qat = moduleC.qat; -foobarbaz.process = moduleE.process; -foobarbaz.reprocess = moduleD.reprocess; -foobarbaz.Bar = moduleC.Bar; -export type Bar = moduleC.Bar; -export interface FizzBuzz { - foo: string; - bar: number; -} diff --git a/tools/ts_library_builder/testdata/lib.extra.d.ts b/tools/ts_library_builder/testdata/lib.extra.d.ts deleted file mode 100644 index 8dc197334..000000000 --- a/tools/ts_library_builder/testdata/lib.extra.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// comment - -declare namespace Qat { - class Foo { - bar: string; - } -} diff --git a/tools/ts_library_builder/testdata/moduleA.ts b/tools/ts_library_builder/testdata/moduleA.ts deleted file mode 100644 index a5bd07e2f..000000000 --- a/tools/ts_library_builder/testdata/moduleA.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** jsdoc for foo */ -export function foo(a: string, b: string) { - console.log(a, b); -} - -// no jsdoc for bar -export async function bar(promise: Promise<void>): Promise<void> { - return promise.then(() => {}); -} diff --git a/tools/ts_library_builder/testdata/moduleB.ts b/tools/ts_library_builder/testdata/moduleB.ts deleted file mode 100644 index 91f5ea875..000000000 --- a/tools/ts_library_builder/testdata/moduleB.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** jsdoc about Foo */ -export class Foo { - private _foo = "foo"; - /** jsdoc about Foo.log() */ - log() { - console.log(this._foo); - return this._foo; - } -} diff --git a/tools/ts_library_builder/testdata/moduleC.ts b/tools/ts_library_builder/testdata/moduleC.ts deleted file mode 100644 index b998c9e9d..000000000 --- a/tools/ts_library_builder/testdata/moduleC.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** jsdoc for Bar */ -export class Bar { - private _bar: string; - /** jsdoc for Bar.log() */ - log() { - console.log(this._bar); - return this.log; - } -} - -/** - * jsdoc for qat - * @param a jsdoc for qat(a) - * @param b jsdoc for qat(b) - */ -export function qat(a: string, b: string) { - return a + b; -} diff --git a/tools/ts_library_builder/testdata/moduleD.ts b/tools/ts_library_builder/testdata/moduleD.ts deleted file mode 100644 index 8752699d1..000000000 --- a/tools/ts_library_builder/testdata/moduleD.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as moduleF from "./moduleF"; - -export function reprocess(value: typeof moduleF.key) { - console.log(value); -} diff --git a/tools/ts_library_builder/testdata/moduleE.ts b/tools/ts_library_builder/testdata/moduleE.ts deleted file mode 100644 index 361a9ad0f..000000000 --- a/tools/ts_library_builder/testdata/moduleE.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as moduleF from "./moduleF"; - -export function process(value: typeof moduleF.key) { - console.log(value); -} diff --git a/tools/ts_library_builder/testdata/moduleF.ts b/tools/ts_library_builder/testdata/moduleF.ts deleted file mode 100644 index b2f8883ad..000000000 --- a/tools/ts_library_builder/testdata/moduleF.ts +++ /dev/null @@ -1 +0,0 @@ -export const key = "value"; diff --git a/tools/ts_library_builder/tsconfig.json b/tools/ts_library_builder/tsconfig.json deleted file mode 100644 index 54db4887e..000000000 --- a/tools/ts_library_builder/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "lib": ["esnext"], - "moduleResolution": "node", - "strict": true, - "target": "esnext" - }, - "files": ["./build_library.ts"] -} |