diff --git a/demo/demo.ts b/demo/demo.ts index dc18d0f9b..cc83f7f87 100644 --- a/demo/demo.ts +++ b/demo/demo.ts @@ -184,6 +184,7 @@ export function toClosureJS( externs: {}, emitSkipped: true, emittedFiles: [], + fileSummaries: new Map(), }; } return tsickle.emit(program, transformerHost, writeFile); diff --git a/src/googmodule.ts b/src/googmodule.ts index 423b5e6be..bf6e6d91b 100644 --- a/src/googmodule.ts +++ b/src/googmodule.ts @@ -89,6 +89,23 @@ export function jsPathToNamespace( host: GoogModuleProcessorHost, context: ts.Node, diagnostics: ts.Diagnostic[], importPath: string, getModuleSymbol: () => ts.Symbol | undefined): string|undefined { + const namespace = localJsPathToNamespace(host, importPath); + if (namespace) return namespace; + + const moduleSymbol = getModuleSymbol(); + if (!moduleSymbol) return; + return getGoogNamespaceFromClutzComments( + context, diagnostics, importPath, moduleSymbol); +} + +/** + * Resolves an import path to its goog namespace, if it points to an original + * closure JavaScript file, using only local information. + * + * Forwards to `jsPathToModuleName` on the host if present. + */ +export function localJsPathToNamespace( + host: GoogModuleProcessorHost, importPath: string): string|undefined { if (importPath.match(/^goog:/)) { // This is a namespace import, of the form "goog:foo.bar". // Fix it to just "foo.bar". @@ -99,10 +116,7 @@ export function jsPathToNamespace( return host.jsPathToModuleName(importPath); } - const moduleSymbol = getModuleSymbol(); - if (!moduleSymbol) return; - return getGoogNamespaceFromClutzComments( - context, diagnostics, importPath, moduleSymbol); + return undefined; } /** diff --git a/src/path.ts b/src/path.ts index c4d69dfa2..e1ed654ff 100644 --- a/src/path.ts +++ b/src/path.ts @@ -47,3 +47,8 @@ export function relative(base: string, rel: string): string { export function normalize(path: string): string { return ts.resolvePath(path); } + +/** Wrapper around ts.resolvePath. */ +export function resolve(path: string, ...paths: string[]): string { + return ts.resolvePath(path, ...paths); +} diff --git a/src/summary.ts b/src/summary.ts new file mode 100644 index 000000000..2aac383b1 --- /dev/null +++ b/src/summary.ts @@ -0,0 +1,125 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/** The Type of an import, as used in JsTrimmer. */ +export enum Type { + UNKNOWN = 0, + /** The symbol type for Closure namespace. */ + CLOSURE, + /** The symbol type for a GSS namespace. */ + GSS, + /** The symbol type for a Soy namespace. */ + SOY, + /** The symbol type for an extensionless google3-relative CSS/GSS path. */ + CSSPATH, + /** The symbol type for a google3-relative ES module path. */ + ESPATH, +} + +/** The module system used by a file. */ +export enum ModuleType { + UNKNOWN = 0, + GOOG_PROVIDE, + GOOG_MODULE, + ES_MODULE, +} + +/** A single imported symbol. */ +export interface Symbol { + type: Type; + name: string; +} + +/** + * The JsTrimmer file summary for a single file. Contains imported and + * exported symbols, as well as some other information required for sorting and + * pruning files. + */ +export class FileSummary { + // These sets are implemented as Maps of jsonified Symbol to Symbol because + // JavaScript Sets use object address, not object contents. Similarly, we use + // getters and setters for these to hide this implementation detail. + private readonly provideSet = new Map(); + private readonly strongRequireSet = new Map(); + private readonly weakRequireSet = new Map(); + private readonly dynamicRequireSet = new Map(); + private readonly modSet = new Map(); + private readonly enhancedSet = new Map(); + toggles: string[] = []; + modName: string|undefined; + autochunk = false; + enhanceable = false; + moduleType = ModuleType.UNKNOWN; + + private stringify(symbol: Symbol): string { + return JSON.stringify(symbol); + } + + addProvide(provide: Symbol) { + this.provideSet.set(this.stringify(provide), provide); + } + + get provides(): Symbol[] { + return [...this.provideSet.values()]; + } + + addStrongRequire(strongRequire: Symbol) { + this.strongRequireSet.set(this.stringify(strongRequire), strongRequire); + } + + get strongRequires(): Symbol[] { + return [...this.strongRequireSet.values()]; + } + + addWeakRequire(weakRequire: Symbol) { + this.weakRequireSet.set(this.stringify(weakRequire), weakRequire); + } + + get weakRequires(): Symbol[] { + return [...this.weakRequireSet.values()]; + } + + addDynamicRequire(dynamicRequire: Symbol) { + this.dynamicRequireSet.set(this.stringify(dynamicRequire), dynamicRequire); + } + + get dynamicRequires(): Symbol[] { + return [...this.dynamicRequireSet.values()]; + } + + addMods(mods: Symbol) { + this.modSet.set(this.stringify(mods), mods); + } + + get mods(): Symbol[] { + return [...this.modSet.values()]; + } + + addEnhanced(enhanced: Symbol) { + this.enhancedSet.set(this.stringify(enhanced), enhanced); + } + + get enhanced(): Symbol[] { + return [...this.enhancedSet.values()]; + } +} + +/** Provides dependencies for file generation. */ +export interface SummaryGenerationProcessorHost { + /** @deprecated use unknownTypesPaths instead */ + typeBlackListPaths?: Set; + /** If provided, a set of paths whose types should always generate as {?}. */ + unknownTypesPaths?: Set; + /** See compiler_host.ts */ + rootDirsRelative(fileName: string): string; + /** + * Whether to convert CommonJS require() imports to goog.module() and + * goog.require() calls. + */ + googmodule: boolean; +} diff --git a/src/ts_migration_exports_shim.ts b/src/ts_migration_exports_shim.ts index 962bc4616..ac715764b 100644 --- a/src/ts_migration_exports_shim.ts +++ b/src/ts_migration_exports_shim.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import {ModulesManifest} from './modules_manifest'; +import {FileSummary, ModuleType, Type} from './summary'; import {getGoogFunctionName, isAnyTsmesCall, isGoogCallExpressionOf, isTsmesDeclareLegacyNamespaceCall, isTsmesShorthandCall, reportDiagnostic} from './transformer_util'; /** Provides dependencies for file generation. */ @@ -46,7 +47,8 @@ export type TsMigrationExportsShimFileMap = Map; export function createTsMigrationExportsShimTransformerFactory( typeChecker: ts.TypeChecker, host: TsMigrationExportsShimProcessorHost, manifest: ModulesManifest, tsickleDiagnostics: ts.Diagnostic[], - outputFileMap: TsMigrationExportsShimFileMap): + outputFileMap: TsMigrationExportsShimFileMap, + fileSummaries: Map): ts.TransformerFactory { return (context: ts.TransformationContext): ts.Transformer => { return (src: ts.SourceFile): ts.SourceFile => { @@ -70,13 +72,17 @@ export function createTsMigrationExportsShimTransformerFactory( // TODO(martinprobst): the empty files might cause issues with code // that should be in mods or modules. outputFileMap.set(tsmesFile, ''); + const fileSummary = new FileSummary(); + fileSummary.moduleType = ModuleType.UNKNOWN; + fileSummaries.set(tsmesFile, fileSummary); if (context.getCompilerOptions().declaration) { outputFileMap.set(dtsFile, ''); } return src; } - const result = generator.generateExportShimJavaScript(); - outputFileMap.set(tsmesFile, result); + const [content, fileSummary] = generator.generateExportShimJavaScript(); + outputFileMap.set(tsmesFile, content); + fileSummaries.set(tsmesFile, fileSummary); if (context.getCompilerOptions().declaration) { const dtsResult = generator.generateExportShimDeclarations(); outputFileMap.set(dtsFile, dtsResult); @@ -396,7 +402,7 @@ class Generator { * * NOTE: This code must be written to be compatible as-is with IE11. */ - generateExportShimJavaScript(): string { + generateExportShimJavaScript(): [string, FileSummary] { if (!this.outputIds || !this.tsmesBreakdown) { throw new Error('tsmes call must be extracted first'); } @@ -427,11 +433,12 @@ class Generator { this.manifest.addReferencedModule( this.outputIds.google3Path, this.srcIds.googModuleId); - const pintoModuleAnnotation = containsAtPintoModule(this.src) ? + const isAutoChunk = containsAtPintoModule(this.src); + const pintoModuleAnnotation = isAutoChunk ? '@pintomodule found in original_file' : 'pintomodule absent in original_file'; - return lines( + const content = lines( '/**', ' * @fileoverview generator:ts_migration_exports_shim.ts', ' * original_file:' + this.srcIds.google3Path, @@ -443,6 +450,18 @@ class Generator { exportsAssignment, '', ); + + const fileSummary = new FileSummary(); + fileSummary.addProvide( + {type: Type.CLOSURE, name: this.outputIds.googModuleId}); + fileSummary.addStrongRequire({type: Type.CLOSURE, name: 'goog'}); + fileSummary.addStrongRequire( + {type: Type.CLOSURE, name: this.srcIds.googModuleId}); + + fileSummary.autochunk = isAutoChunk; + fileSummary.moduleType = ModuleType.GOOG_MODULE; + + return [content, fileSummary]; } /** diff --git a/src/tsickle.ts b/src/tsickle.ts index 6898dd6c5..3b10f073a 100644 --- a/src/tsickle.ts +++ b/src/tsickle.ts @@ -20,6 +20,7 @@ import * as googmodule from './googmodule'; import {jsdocTransformer, removeTypeAssertions} from './jsdoc_transformer'; import {ModulesManifest} from './modules_manifest'; import {namespaceTransformer} from './ns_transformer'; +import {FileSummary, SummaryGenerationProcessorHost} from './summary'; import {isDtsFileName} from './transformer_util'; import * as tsmes from './ts_migration_exports_shim'; import {makeTsickleDeclarationMarkerTransformerFactory} from './tsickle_declaration_marker'; @@ -29,10 +30,12 @@ export {pathToModuleName} from './cli_support'; // Retained here for API compatibility. export {getGeneratedExterns} from './externs'; export {FileMap, ModulesManifest} from './modules_manifest'; +export {FileSummary, ModuleType, Symbol, Type} from './summary'; export interface TsickleHost extends googmodule.GoogModuleProcessorHost, tsmes.TsMigrationExportsShimProcessorHost, - AnnotatorHost { + AnnotatorHost, + SummaryGenerationProcessorHost { /** * Whether to downlevel decorators */ @@ -79,6 +82,11 @@ export interface TsickleHost extends googmodule.GoogModuleProcessorHost, * Whether to add suppressions by default. */ generateExtraSuppressions: boolean; + + /** + * Whether to generate summaries. + */ + generateSummary?: boolean; } @@ -90,6 +98,7 @@ export function mergeEmitResults(emitResults: EmitResult[]): EmitResult { {[fileName: string]: {output: string, moduleNamespace: string}} = {}; const modulesManifest = new ModulesManifest(); const tsMigrationExportsShimFiles = new Map(); + const fileSummaries = new Map(); for (const er of emitResults) { diagnostics.push(...er.diagnostics); emitSkipped = emitSkipped || er.emitSkipped; @@ -101,6 +110,9 @@ export function mergeEmitResults(emitResults: EmitResult[]): EmitResult { for (const [k, v] of er.tsMigrationExportsShimFiles) { tsMigrationExportsShimFiles.set(k, v); } + for (const [k, v] of er.fileSummaries) { + fileSummaries.set(k, v); + } } return { @@ -110,6 +122,7 @@ export function mergeEmitResults(emitResults: EmitResult[]): EmitResult { externs, tsMigrationExportsShimFiles, modulesManifest, + fileSummaries, }; } @@ -127,6 +140,8 @@ export interface EmitResult extends ts.EmitResult { * Filenames are google3 relative. */ tsMigrationExportsShimFiles: tsmes.TsMigrationExportsShimFileMap; + + fileSummaries: Map; } export interface EmitTransformers { @@ -182,6 +197,7 @@ export function emit( modulesManifest: new ModulesManifest(), externs: {}, tsMigrationExportsShimFiles: new Map(), + fileSummaries: new Map(), }; } @@ -189,10 +205,11 @@ export function emit( const tsMigrationExportsShimFiles = new Map(); const tsickleSourceTransformers: Array> = []; + const fileSummaries = new Map(); tsickleSourceTransformers.push( tsmes.createTsMigrationExportsShimTransformerFactory( typeChecker, host, modulesManifest, tsickleDiagnostics, - tsMigrationExportsShimFiles)); + tsMigrationExportsShimFiles, fileSummaries)); if (host.transformTypesToClosure) { // Only add @suppress {checkTypes} comments when also adding type @@ -282,6 +299,7 @@ export function emit( diagnostics: [...tsDiagnostics, ...tsickleDiagnostics], externs, tsMigrationExportsShimFiles, + fileSummaries, }; }