Comparing version 0.23.3 to 0.23.4
@@ -1,14 +0,63 @@ | ||
/** | ||
* @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 | ||
*/ | ||
import { SourceMapGenerator } from 'source-map'; | ||
import * as ts from 'typescript'; | ||
export declare function convertDecorators(typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile): { | ||
import { Rewriter } from './rewriter'; | ||
import { SourceMapper } from './source_map_utils'; | ||
export declare class DecoratorClassVisitor { | ||
private typeChecker; | ||
private rewriter; | ||
private classDecl; | ||
private importedNames; | ||
/** Decorators on the class itself. */ | ||
decorators: ts.Decorator[]; | ||
/** The constructor parameter list and decorators on each param. */ | ||
ctorParameters: Array<[[ts.Symbol, string] | undefined, ts.Decorator[] | undefined] | null>; | ||
/** Per-method decorators. */ | ||
propDecorators: Map<string, ts.Decorator[]>; | ||
constructor(typeChecker: ts.TypeChecker, rewriter: Rewriter, classDecl: ts.ClassDeclaration, importedNames: Array<{ | ||
name: ts.Identifier; | ||
declarationNames: ts.Identifier[]; | ||
}>); | ||
/** | ||
* Determines whether the given decorator should be re-written as an annotation. | ||
*/ | ||
private shouldLower(decorator); | ||
private decoratorsToLower(n); | ||
/** | ||
* gatherConstructor grabs the parameter list and decorators off the class | ||
* constructor, and emits nothing. | ||
*/ | ||
private gatherConstructor(ctor); | ||
/** | ||
* gatherMethod grabs the decorators off a class method and emits nothing. | ||
*/ | ||
private gatherMethodOrProperty(method); | ||
/** | ||
* For lowering decorators, we need to refer to constructor types. | ||
* So we start with the identifiers that represent these types. | ||
* However, TypeScript does not allow use to emit them in a value position | ||
* as it associated different symbol information with it. | ||
* | ||
* This method looks for the place where the value that is associated to | ||
* the type is defined and returns that identifier instead. | ||
* | ||
* @param typeSymbol | ||
* @return The identifier | ||
*/ | ||
private getValueIdentifierForType(typeSymbol); | ||
beforeProcessNode(node: ts.Node): void; | ||
maybeProcessDecorator(node: ts.Decorator, start?: number): boolean; | ||
/** | ||
* emits the types for the various gathered metadata to be used | ||
* in the tsickle type annotations helper. | ||
*/ | ||
emitMetadataTypeAnnotationsHelpers(): void; | ||
/** | ||
* emitMetadata emits the various gathered metadata, as static fields. | ||
*/ | ||
emitMetadataAsStaticProperties(): void; | ||
private emitDecorator(decorator); | ||
} | ||
export declare function visitClassContentIncludingDecorators(classDecl: ts.ClassDeclaration, rewriter: Rewriter, decoratorVisitor?: DecoratorClassVisitor): void; | ||
export declare function convertDecorators(typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile, sourceMapper?: SourceMapper): { | ||
output: string; | ||
diagnostics: ts.Diagnostic[]; | ||
sourceMap: SourceMapGenerator; | ||
}; |
@@ -0,1 +1,20 @@ | ||
import { ModulesManifest } from './modules_manifest'; | ||
export interface Es5ProcessorHost { | ||
/** | ||
* Takes a context (the current file) and the path of the file to import | ||
* and generates a googmodule module name | ||
*/ | ||
pathToModuleName(context: string, importPath: string): string; | ||
/** | ||
* If we do googmodule processing, we polyfill module.id, since that's | ||
* part of ES6 modules. This function determines what the module.id will be | ||
* for each file. | ||
*/ | ||
fileNameToModuleId(fileName: string): string; | ||
} | ||
export interface Es5ProcessorOptions { | ||
googmodule?: boolean; | ||
es5Mode?: boolean; | ||
prelude?: string; | ||
} | ||
/** | ||
@@ -20,5 +39,6 @@ * Extracts the namespace part of a goog: import, or returns null if the given | ||
*/ | ||
export declare function processES5(fileName: string, moduleId: string, content: string, pathToModuleName: (context: string, fileName: string) => string, isES5?: boolean, prelude?: string): { | ||
export declare function processES5(host: Es5ProcessorHost, options: Es5ProcessorOptions, fileName: string, content: string): { | ||
output: string; | ||
referencedModules: string[]; | ||
}; | ||
export declare function convertCommonJsToGoogModuleIfNeeded(host: Es5ProcessorHost, options: Es5ProcessorOptions, modulesManifest: ModulesManifest, fileName: string, content: string): string; |
/** | ||
* @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 | ||
*/ | ||
/** | ||
* TypeScript has an API for JSDoc already, but it's not exposed. | ||
@@ -10,0 +3,0 @@ * https://github.com/Microsoft/TypeScript/issues/7393 |
@@ -17,2 +17,3 @@ /** | ||
private referencedModules; | ||
addManifest(other: ModulesManifest): void; | ||
addModule(fileName: string, module: string): void; | ||
@@ -19,0 +20,0 @@ addReferencedModule(fileName: string, resolvedModule: string): void; |
@@ -8,4 +8,4 @@ /** | ||
*/ | ||
import { SourceMapGenerator } from 'source-map'; | ||
import * as ts from 'typescript'; | ||
import { SourceMapper } from './source_map_utils'; | ||
/** | ||
@@ -17,8 +17,7 @@ * A Rewriter manages iterating through a ts.SourceFile, copying input | ||
export declare abstract class Rewriter { | ||
protected file: ts.SourceFile; | ||
file: ts.SourceFile; | ||
private sourceMapper; | ||
private output; | ||
/** Errors found while examining the code. */ | ||
protected diagnostics: ts.Diagnostic[]; | ||
/** The source map that's generated while rewriting this file. */ | ||
private sourceMap; | ||
/** Current position in the output. */ | ||
@@ -31,7 +30,11 @@ private position; | ||
private indent; | ||
constructor(file: ts.SourceFile); | ||
/** | ||
* Skip emitting any code before the given offset. E.g. used to avoid emitting @fileoverview | ||
* comments twice. | ||
*/ | ||
private skipUpToOffset; | ||
constructor(file: ts.SourceFile, sourceMapper?: SourceMapper); | ||
getOutput(): { | ||
output: string; | ||
diagnostics: ts.Diagnostic[]; | ||
sourceMap: SourceMapGenerator; | ||
}; | ||
@@ -50,9 +53,5 @@ /** | ||
/** writeNode writes a ts.Node, calling this.visit() on its children. */ | ||
writeNode(node: ts.Node, skipComments?: boolean): void; | ||
writeNode(node: ts.Node, skipComments?: boolean, newLineIfCommentsStripped?: boolean): void; | ||
writeNodeFrom(node: ts.Node, pos: number, end?: number): void; | ||
/** | ||
* Skip emitting any code before the given offset. Used to avoid emitting @fileoverview comments | ||
* twice. | ||
*/ | ||
protected skipUpToOffset: number; | ||
/** | ||
* Write a span of the input file as expressed by absolute offsets. | ||
@@ -62,3 +61,3 @@ * These offsets are found in attributes like node.getFullStart() and | ||
*/ | ||
writeRange(from: number, to: number): void; | ||
writeRange(node: ts.Node, from: number, to: number): void; | ||
emit(str: string): void; | ||
@@ -65,0 +64,0 @@ /** Removes comment metacharacters from a string, to make it safe to embed in a comment. */ |
@@ -8,3 +8,4 @@ /** | ||
*/ | ||
import { SourceMapConsumer, SourceMapGenerator } from 'source-map'; | ||
import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; | ||
import * as ts from 'typescript'; | ||
export declare function containsInlineSourceMap(source: string): boolean; | ||
@@ -19,2 +20,3 @@ export declare function getInlineSourceMapCount(source: string): number; | ||
export declare function setInlineSourceMap(source: string, sourceMap: string): string; | ||
export declare function parseSourceMap(text: string, fileName?: string, sourceName?: string): RawSourceMap; | ||
export declare function sourceMapConsumerToGenerator(sourceMapConsumer: SourceMapConsumer): SourceMapGenerator; | ||
@@ -30,1 +32,17 @@ /** | ||
export declare function sourceMapTextToGenerator(sourceMapText: string): SourceMapGenerator; | ||
export interface SourcePosition { | ||
column: number; | ||
line: number; | ||
position: number; | ||
} | ||
export interface SourceMapper { | ||
addMapping(originalNode: ts.Node, original: SourcePosition, generated: SourcePosition, length: number): void; | ||
} | ||
export declare const NOOP_SOURCE_MAPPER: SourceMapper; | ||
export declare class DefaultSourceMapper implements SourceMapper { | ||
private fileName; | ||
/** The source map that's generated while rewriting this file. */ | ||
sourceMap: SourceMapGenerator; | ||
constructor(fileName: string); | ||
addMapping(node: ts.Node, original: SourcePosition, generated: SourcePosition, length: number): void; | ||
} |
import * as ts from 'typescript'; | ||
import { Es5ProcessorHost, Es5ProcessorOptions } from './es5processor'; | ||
import { ModulesManifest } from './modules_manifest'; | ||
import * as tsickle from './tsickle'; | ||
/** | ||
@@ -12,28 +14,17 @@ * Tsickle can perform 2 different precompilation transforms - decorator downleveling | ||
NONE = 0, | ||
DECORATOR_DOWNLEVEL = 1, | ||
CLOSURIZE = 2, | ||
} | ||
export interface Options { | ||
googmodule?: boolean; | ||
es5Mode?: boolean; | ||
prelude?: string; | ||
/** | ||
* If true, convert every type to the Closure {?} type, which means | ||
* "don't check types". | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
untyped?: boolean; | ||
DECORATOR_DOWNLEVEL = 1, | ||
/** | ||
* If provided a function that logs an internal warning. | ||
* These warnings are not actionable by an end user and should be hidden | ||
* by default. | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
logWarning?: (warning: ts.Diagnostic) => void; | ||
/** If provided, a set of paths whose types should always generate as {?}. */ | ||
typeBlackListPaths?: Set<string>; | ||
/** | ||
* Convert shorthand "/index" imports to full path (include the "/index"). | ||
* Annotation will be slower because every import must be resolved. | ||
*/ | ||
convertIndexImportShorthand?: boolean; | ||
CLOSURIZE = 2, | ||
DECORATOR_DOWNLEVEL_AND_CLOSURIZE = 3, | ||
} | ||
export interface Options extends Es5ProcessorOptions, tsickle.AnnotatorOptions { | ||
logWarning?: TsickleHost['logWarning']; | ||
} | ||
/** | ||
@@ -43,3 +34,3 @@ * Provides hooks to customize TsickleCompilerHost's behavior for different | ||
*/ | ||
export interface TsickleHost { | ||
export interface TsickleHost extends Es5ProcessorHost, tsickle.AnnotatorHost { | ||
/** | ||
@@ -51,7 +42,2 @@ * If true, tsickle and decorator downlevel processing will be skipped for | ||
/** | ||
* Takes a context (the current file) and the path of the file to import | ||
* and generates a googmodule module name | ||
*/ | ||
pathToModuleName(context: string, importPath: string): string; | ||
/** | ||
* Tsickle treats warnings as errors, if true, ignore warnings. This might be | ||
@@ -61,8 +47,2 @@ * useful for e.g. third party code. | ||
shouldIgnoreWarningsForPath(filePath: string): boolean; | ||
/** | ||
* If we do googmodule processing, we polyfill module.id, since that's | ||
* part of ES6 modules. This function determines what the module.id will be | ||
* for each file. | ||
*/ | ||
fileNameToModuleId(fileName: string): string; | ||
} | ||
@@ -111,3 +91,3 @@ /** | ||
private downlevelDecorators(sourceFile, program, fileName, languageVersion); | ||
private closurize(sourceFile, program, fileName, languageVersion); | ||
private closurize(sourceFile, program, fileName, languageVersion, downlevelDecorators); | ||
/** Concatenate all generated externs definitions together into a string. */ | ||
@@ -114,0 +94,0 @@ getGeneratedExterns(): string; |
@@ -1,18 +0,86 @@ | ||
import { SourceMapGenerator } from 'source-map'; | ||
import * as ts from 'typescript'; | ||
import { Options } from './tsickle_compiler_host'; | ||
import { SourceMapper } from './source_map_utils'; | ||
export { convertDecorators } from './decorator-annotator'; | ||
export { processES5 } from './es5processor'; | ||
export { FileMap, ModulesManifest } from './modules_manifest'; | ||
export { EmitResult, EmitTransformers, emitWithTsickle, mergeEmitResults, TransformerHost, TransformerOptions } from './transformer'; | ||
export { Options, Pass, TsickleCompilerHost, TsickleHost } from './tsickle_compiler_host'; | ||
export interface Output { | ||
/** The TypeScript source with Closure annotations inserted. */ | ||
output: string; | ||
/** Generated externs declarations, if any. */ | ||
externs: string | null; | ||
/** Error messages, if any. */ | ||
diagnostics: ts.Diagnostic[]; | ||
/** A source map mapping back into the original sources. */ | ||
sourceMap: SourceMapGenerator; | ||
export interface AnnotatorHost { | ||
/** | ||
* If provided a function that logs an internal warning. | ||
* These warnings are not actionable by an end user and should be hidden | ||
* by default. | ||
*/ | ||
logWarning?: (warning: ts.Diagnostic) => void; | ||
pathToModuleName: (context: string, importPath: string) => string; | ||
} | ||
export interface AnnotatorOptions { | ||
/** | ||
* If true, convert every type to the Closure {?} type, which means | ||
* "don't check types". | ||
*/ | ||
untyped?: boolean; | ||
/** If provided, a set of paths whose types should always generate as {?}. */ | ||
typeBlackListPaths?: Set<string>; | ||
/** | ||
* Convert shorthand "/index" imports to full path (include the "/index"). | ||
* Annotation will be slower because every import must be resolved. | ||
*/ | ||
convertIndexImportShorthand?: boolean; | ||
} | ||
export declare enum AnnotatorFeatures { | ||
LowerDecorators = 1, | ||
/** | ||
* Filter out types when expanding `export * from ...`. | ||
* | ||
* Needed by the transformer version as TypeScript collects symbol information before | ||
* running the transformers, and the generated identifiers therefore have no | ||
* TypeScript symbol information associated. Because of this, Typescript does not | ||
* elide exports for types in this case. | ||
* | ||
* This flag will be removed and always set once we drop the on transformer version of tsickle. | ||
*/ | ||
FilterTypesInExportStart = 2, | ||
/** | ||
* Emit enums as | ||
* ``` | ||
* type EnumX = number; | ||
* const EnumX = {}; | ||
* export {EnumX}; | ||
* ``` | ||
* and not as | ||
* ``` | ||
* export type EnumX = number; | ||
* export const EnumX = {}; | ||
* ``` | ||
* | ||
* Needed for the transformer version of tsickle as the changed enum declarations no longer | ||
* have TypeScript symbol | ||
* information and thefore typescript no longer generates `export.` for all property accesses | ||
* to the exported symbols. | ||
* | ||
* This flag cannot be used in the non transformer version of tsickle, | ||
* as then the ordering of enum declaration and usage of the enum matters: | ||
* if a file declares an interfaces | ||
* that uses an enum, now the enum has to be declared | ||
* before the interface or otherwise TypeScript will | ||
* complain about "...of exported interface has or is using private name...". | ||
* TODO(tbosch): file an issue with TypeScript, tracked in | ||
* https://github.com/angular/tsickle/issues/528. | ||
* | ||
* This flag will be removed and always set once we drop the on transformer version of tsickle. | ||
*/ | ||
SimpleEnum = 4, | ||
/** | ||
* Generated @typedefs for reexported interfaces. | ||
* | ||
* Needed for the transformer version of tsickle as the .d.ts is generated before running tsickle, | ||
* and therefore does no longer contain the reexports for the interfaces, even if | ||
* tsickle changes them into functions. | ||
* | ||
* This flag will be removed and always set once we drop the on transformer version of tsickle. | ||
*/ | ||
TypeDefReexportForInterfaces = 8, | ||
Default = 0, | ||
Transformer = 15, | ||
} | ||
/** | ||
@@ -30,3 +98,16 @@ * The header to be used in generated externs. This is not included in the | ||
export declare function formatDiagnostics(diags: ts.Diagnostic[]): string; | ||
/** @return true if node has the specified modifier flag set. */ | ||
export declare function hasModifierFlag(node: ts.Node, flag: ts.ModifierFlags): boolean; | ||
export declare function isDtsFileName(fileName: string): boolean; | ||
export declare function annotate(program: ts.Program, file: ts.SourceFile, pathToModuleName: (context: string, importPath: string) => string, options?: Options, host?: ts.ModuleResolutionHost, tsOpts?: ts.CompilerOptions): Output; | ||
export declare function annotate(typeChecker: ts.TypeChecker, file: ts.SourceFile, host: AnnotatorHost, options?: AnnotatorOptions, tsHost?: ts.ModuleResolutionHost, tsOpts?: ts.CompilerOptions, sourceMapper?: SourceMapper, features?: AnnotatorFeatures): { | ||
output: string; | ||
diagnostics: ts.Diagnostic[]; | ||
}; | ||
export declare function writeExterns(typeChecker: ts.TypeChecker, file: ts.SourceFile, host: AnnotatorHost, options?: AnnotatorOptions): { | ||
output: string; | ||
diagnostics: ts.Diagnostic[]; | ||
}; | ||
/** Concatenate all generated externs definitions together into a string. */ | ||
export declare function getGeneratedExterns(externs: { | ||
[fileName: string]: string; | ||
}): string; |
@@ -24,1 +24,5 @@ /** | ||
export declare function createOutputRetainingCompilerHost(outputFiles: Map<string, string>, delegate: ts.CompilerHost): ts.CompilerHost; | ||
/** | ||
* Returns the input string with line endings normalized to '\n'. | ||
*/ | ||
export declare function normalizeLineEndings(input: string): string; |
@@ -25,11 +25,16 @@ "use strict"; | ||
var util_1 = require("./util"); | ||
// ClassRewriter rewrites a single "class Foo {...}" declaration. | ||
// DecoratorClassVisitor rewrites a single "class Foo {...}" declaration. | ||
// It's its own object because we collect decorators on the class and the ctor | ||
// separately for each class we encounter. | ||
var ClassRewriter = (function (_super) { | ||
__extends(ClassRewriter, _super); | ||
function ClassRewriter(typeChecker, sourceFile) { | ||
var _this = _super.call(this, sourceFile) || this; | ||
_this.typeChecker = typeChecker; | ||
return _this; | ||
var DecoratorClassVisitor = (function () { | ||
function DecoratorClassVisitor(typeChecker, rewriter, classDecl, importedNames) { | ||
this.typeChecker = typeChecker; | ||
this.rewriter = rewriter; | ||
this.classDecl = classDecl; | ||
this.importedNames = importedNames; | ||
if (classDecl.decorators) { | ||
var toLower = this.decoratorsToLower(classDecl); | ||
if (toLower.length > 0) | ||
this.decorators = toLower; | ||
} | ||
} | ||
@@ -39,3 +44,3 @@ /** | ||
*/ | ||
ClassRewriter.prototype.shouldLower = function (decorator) { | ||
DecoratorClassVisitor.prototype.shouldLower = function (decorator) { | ||
for (var _i = 0, _a = decorators_1.getDecoratorDeclarations(decorator, this.typeChecker); _i < _a.length; _i++) { | ||
@@ -75,3 +80,3 @@ var d = _a[_i]; | ||
}; | ||
ClassRewriter.prototype.decoratorsToLower = function (n) { | ||
DecoratorClassVisitor.prototype.decoratorsToLower = function (n) { | ||
var _this = this; | ||
@@ -84,39 +89,6 @@ if (n.decorators) { | ||
/** | ||
* process is the main entry point, rewriting a single class node. | ||
*/ | ||
ClassRewriter.prototype.process = function (node) { | ||
var _this = this; | ||
if (node.decorators) { | ||
var toLower = this.decoratorsToLower(node); | ||
if (toLower.length > 0) | ||
this.decorators = toLower; | ||
} | ||
// Emit the class contents, but stop just before emitting the closing curly brace. | ||
// (This code is the same as Rewriter.writeNode except for the curly brace handling.) | ||
var pos = node.getFullStart(); | ||
ts.forEachChild(node, function (child) { | ||
// This forEachChild handles emitting the text between each child, while child.visit | ||
// recursively emits the children themselves. | ||
_this.writeRange(pos, child.getFullStart()); | ||
_this.visit(child); | ||
pos = child.getEnd(); | ||
}); | ||
// At this point, we've emitted up through the final child of the class, so all that | ||
// remains is the trailing whitespace and closing curly brace. | ||
// The final character owned by the class node should always be a '}', | ||
// or we somehow got the AST wrong and should report an error. | ||
// (Any whitespace or semicolon following the '}' will be part of the next Node.) | ||
if (this.file.text[node.getEnd() - 1] !== '}') { | ||
this.error(node, 'unexpected class terminator'); | ||
} | ||
this.writeRange(pos, node.getEnd() - 1); | ||
this.emitMetadata(); | ||
this.emit('}'); | ||
return this.getOutput(); | ||
}; | ||
/** | ||
* gatherConstructor grabs the parameter list and decorators off the class | ||
* constructor, and emits nothing. | ||
*/ | ||
ClassRewriter.prototype.gatherConstructor = function (ctor) { | ||
DecoratorClassVisitor.prototype.gatherConstructor = function (ctor) { | ||
var ctorParameters = []; | ||
@@ -137,4 +109,5 @@ var hasDecoratedParam = false; | ||
if (sym && (sym.flags & ts.SymbolFlags.Value)) { | ||
paramCtor = new type_translator_1.TypeTranslator(this.typeChecker, param.type) | ||
var typeStr = new type_translator_1.TypeTranslator(this.typeChecker, param.type) | ||
.symbolToString(sym, /* useFqn */ true); | ||
paramCtor = [sym, typeStr]; | ||
} | ||
@@ -157,3 +130,3 @@ } | ||
*/ | ||
ClassRewriter.prototype.gatherMethodOrProperty = function (method) { | ||
DecoratorClassVisitor.prototype.gatherMethodOrProperty = function (method) { | ||
if (!method.decorators) | ||
@@ -164,3 +137,3 @@ return; | ||
// [Symbol.foo]() {...} | ||
this.error(method, 'cannot process decorators on strangely named method'); | ||
this.rewriter.error(method, 'cannot process decorators on strangely named method'); | ||
return; | ||
@@ -177,17 +150,37 @@ } | ||
/** | ||
* maybeProcess is called by the traversal of the AST. | ||
* @return True if the node was handled, false to have the node emitted as normal. | ||
* For lowering decorators, we need to refer to constructor types. | ||
* So we start with the identifiers that represent these types. | ||
* However, TypeScript does not allow use to emit them in a value position | ||
* as it associated different symbol information with it. | ||
* | ||
* This method looks for the place where the value that is associated to | ||
* the type is defined and returns that identifier instead. | ||
* | ||
* @param typeSymbol | ||
* @return The identifier | ||
*/ | ||
ClassRewriter.prototype.maybeProcess = function (node) { | ||
DecoratorClassVisitor.prototype.getValueIdentifierForType = function (typeSymbol) { | ||
if (!typeSymbol.valueDeclaration) { | ||
return null; | ||
} | ||
var valueName = typeSymbol.valueDeclaration.name; | ||
if (!valueName || valueName.kind !== ts.SyntaxKind.Identifier) { | ||
return null; | ||
} | ||
if (valueName.getSourceFile() === this.rewriter.file) { | ||
return valueName; | ||
} | ||
for (var i = 0; i < this.importedNames.length; i++) { | ||
var _a = this.importedNames[i], name_1 = _a.name, declarationNames = _a.declarationNames; | ||
if (declarationNames.some(function (d) { return d === valueName; })) { | ||
return name_1; | ||
} | ||
} | ||
return null; | ||
}; | ||
DecoratorClassVisitor.prototype.beforeProcessNode = function (node) { | ||
switch (node.kind) { | ||
case ts.SyntaxKind.ClassDeclaration: | ||
// Encountered a new class while processing this class; use a new separate | ||
// rewriter to gather+emit its metadata. | ||
var _a = new ClassRewriter(this.typeChecker, this.file).process(node), output = _a.output, diagnostics = _a.diagnostics; | ||
(_b = this.diagnostics).push.apply(_b, diagnostics); | ||
this.emit(output); | ||
return true; | ||
case ts.SyntaxKind.Constructor: | ||
this.gatherConstructor(node); | ||
return false; // Proceed with ordinary emit of the ctor. | ||
break; | ||
case ts.SyntaxKind.PropertyDeclaration: | ||
@@ -198,35 +191,61 @@ case ts.SyntaxKind.SetAccessor: | ||
this.gatherMethodOrProperty(node); | ||
return false; // Proceed with ordinary emit of the method. | ||
case ts.SyntaxKind.Decorator: | ||
if (this.shouldLower(node)) { | ||
// Return true to signal that this node should not be emitted, | ||
// but still emit the whitespace *before* the node. | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
return true; | ||
} | ||
return false; | ||
break; | ||
default: | ||
return false; | ||
} | ||
var _b; | ||
}; | ||
DecoratorClassVisitor.prototype.maybeProcessDecorator = function (node, start) { | ||
if (this.shouldLower(node)) { | ||
// Return true to signal that this node should not be emitted, | ||
// but still emit the whitespace *before* the node. | ||
if (!start) { | ||
start = node.getFullStart(); | ||
} | ||
this.rewriter.writeRange(node, start, node.getStart()); | ||
return true; | ||
} | ||
return false; | ||
}; | ||
/** | ||
* emits the types for the various gathered metadata to be used | ||
* in the tsickle type annotations helper. | ||
*/ | ||
DecoratorClassVisitor.prototype.emitMetadataTypeAnnotationsHelpers = function () { | ||
if (!this.classDecl.name) | ||
return; | ||
var className = rewriter_1.getIdentifierText(this.classDecl.name); | ||
if (this.decorators) { | ||
this.rewriter.emit("/** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */\n"); | ||
this.rewriter.emit(className + ".decorators;\n"); | ||
} | ||
if (this.decorators || this.ctorParameters) { | ||
this.rewriter.emit("/**\n"); | ||
this.rewriter.emit(" * @nocollapse\n"); | ||
this.rewriter.emit(" * @type {function(): !Array<(null|{type: ?, decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>)})>}\n"); | ||
this.rewriter.emit(" */\n"); | ||
this.rewriter.emit(className + ".ctorParameters;\n"); | ||
} | ||
if (this.propDecorators) { | ||
this.rewriter.emit("/** @type {!Object<string,!Array<{type: !Function, args: (undefined|!Array<?>)}>>} */\n"); | ||
this.rewriter.emit(className + ".propDecorators;\n"); | ||
} | ||
}; | ||
/** | ||
* emitMetadata emits the various gathered metadata, as static fields. | ||
*/ | ||
ClassRewriter.prototype.emitMetadata = function () { | ||
DecoratorClassVisitor.prototype.emitMetadataAsStaticProperties = function () { | ||
var decoratorInvocations = '{type: Function, args?: any[]}[]'; | ||
if (this.decorators) { | ||
this.emit("static decorators: " + decoratorInvocations + " = [\n"); | ||
this.rewriter.emit("static decorators: " + decoratorInvocations + " = [\n"); | ||
for (var _i = 0, _a = this.decorators; _i < _a.length; _i++) { | ||
var annotation = _a[_i]; | ||
this.emitDecorator(annotation); | ||
this.emit(',\n'); | ||
this.rewriter.emit(',\n'); | ||
} | ||
this.emit('];\n'); | ||
this.rewriter.emit('];\n'); | ||
} | ||
if (this.decorators || this.ctorParameters) { | ||
this.emit("/** @nocollapse */\n"); | ||
this.rewriter.emit("/** @nocollapse */\n"); | ||
// ctorParameters may contain forward references in the type: field, so wrap in a function | ||
// closure | ||
this.emit("static ctorParameters: () => ({type: any, decorators?: " + decoratorInvocations + | ||
this.rewriter.emit("static ctorParameters: () => ({type: any, decorators?: " + decoratorInvocations + | ||
"}|null)[] = () => [\n"); | ||
@@ -236,37 +255,54 @@ for (var _b = 0, _c = this.ctorParameters || []; _b < _c.length; _b++) { | ||
if (!param) { | ||
this.emit('null,\n'); | ||
this.rewriter.emit('null,\n'); | ||
continue; | ||
} | ||
var ctor = param[0], decorators = param[1]; | ||
this.emit("{type: " + ctor + ", "); | ||
this.rewriter.emit("{type: "); | ||
if (!ctor) { | ||
this.rewriter.emit("undefined"); | ||
} | ||
else { | ||
var typeSymbol = ctor[0], typeStr = ctor[1]; | ||
var emitNode = void 0; | ||
if (typeSymbol) { | ||
emitNode = this.getValueIdentifierForType(typeSymbol); | ||
} | ||
if (emitNode) { | ||
this.rewriter.writeRange(emitNode, emitNode.getStart(), emitNode.getEnd()); | ||
} | ||
else { | ||
this.rewriter.emit(typeStr); | ||
} | ||
} | ||
this.rewriter.emit(", "); | ||
if (decorators) { | ||
this.emit('decorators: ['); | ||
this.rewriter.emit('decorators: ['); | ||
for (var _d = 0, decorators_2 = decorators; _d < decorators_2.length; _d++) { | ||
var decorator = decorators_2[_d]; | ||
this.emitDecorator(decorator); | ||
this.emit(', '); | ||
this.rewriter.emit(', '); | ||
} | ||
this.emit(']'); | ||
this.rewriter.emit(']'); | ||
} | ||
this.emit('},\n'); | ||
this.rewriter.emit('},\n'); | ||
} | ||
this.emit("];\n"); | ||
this.rewriter.emit("];\n"); | ||
} | ||
if (this.propDecorators) { | ||
this.emit("static propDecorators: {[key: string]: " + decoratorInvocations + "} = {\n"); | ||
this.rewriter.emit("static propDecorators: {[key: string]: " + decoratorInvocations + "} = {\n"); | ||
for (var _e = 0, _f = util_1.toArray(this.propDecorators.keys()); _e < _f.length; _e++) { | ||
var name_1 = _f[_e]; | ||
this.emit("'" + name_1 + "': ["); | ||
for (var _g = 0, _h = this.propDecorators.get(name_1); _g < _h.length; _g++) { | ||
var name_2 = _f[_e]; | ||
this.rewriter.emit("\"" + name_2 + "\": ["); | ||
for (var _g = 0, _h = this.propDecorators.get(name_2); _g < _h.length; _g++) { | ||
var decorator = _h[_g]; | ||
this.emitDecorator(decorator); | ||
this.emit(','); | ||
this.rewriter.emit(','); | ||
} | ||
this.emit('],\n'); | ||
this.rewriter.emit('],\n'); | ||
} | ||
this.emit('};\n'); | ||
this.rewriter.emit('};\n'); | ||
} | ||
}; | ||
ClassRewriter.prototype.emitDecorator = function (decorator) { | ||
this.emit('{ type: '); | ||
DecoratorClassVisitor.prototype.emitDecorator = function (decorator) { | ||
this.rewriter.emit('{ type: '); | ||
var expr = decorator.expression; | ||
@@ -276,3 +312,3 @@ switch (expr.kind) { | ||
// The decorator was a plain @Foo. | ||
this.visit(expr); | ||
this.rewriter.visit(expr); | ||
break; | ||
@@ -282,25 +318,26 @@ case ts.SyntaxKind.CallExpression: | ||
var call = expr; | ||
this.visit(call.expression); | ||
this.rewriter.visit(call.expression); | ||
if (call.arguments.length) { | ||
this.emit(', args: ['); | ||
this.rewriter.emit(', args: ['); | ||
for (var _i = 0, _a = call.arguments; _i < _a.length; _i++) { | ||
var arg = _a[_i]; | ||
this.emit(arg.getText()); | ||
this.emit(', '); | ||
this.rewriter.writeNodeFrom(arg, arg.getStart()); | ||
this.rewriter.emit(', '); | ||
} | ||
this.emit(']'); | ||
this.rewriter.emit(']'); | ||
} | ||
break; | ||
default: | ||
this.errorUnimplementedKind(expr, 'gathering metadata'); | ||
this.emit('undefined'); | ||
this.rewriter.errorUnimplementedKind(expr, 'gathering metadata'); | ||
this.rewriter.emit('undefined'); | ||
} | ||
this.emit(' }'); | ||
this.rewriter.emit(' }'); | ||
}; | ||
return ClassRewriter; | ||
}(rewriter_1.Rewriter)); | ||
return DecoratorClassVisitor; | ||
}()); | ||
exports.DecoratorClassVisitor = DecoratorClassVisitor; | ||
var DecoratorRewriter = (function (_super) { | ||
__extends(DecoratorRewriter, _super); | ||
function DecoratorRewriter(typeChecker, sourceFile) { | ||
var _this = _super.call(this, sourceFile) || this; | ||
function DecoratorRewriter(typeChecker, sourceFile, sourceMapper) { | ||
var _this = _super.call(this, sourceFile, sourceMapper) || this; | ||
_this.typeChecker = typeChecker; | ||
@@ -314,7 +351,16 @@ return _this; | ||
DecoratorRewriter.prototype.maybeProcess = function (node) { | ||
if (this.currentDecoratorConverter) { | ||
this.currentDecoratorConverter.beforeProcessNode(node); | ||
} | ||
switch (node.kind) { | ||
case ts.SyntaxKind.Decorator: | ||
return this.currentDecoratorConverter && | ||
this.currentDecoratorConverter.maybeProcessDecorator(node); | ||
case ts.SyntaxKind.ClassDeclaration: | ||
var _a = new ClassRewriter(this.typeChecker, this.file).process(node), output = _a.output, diagnostics = _a.diagnostics; | ||
(_b = this.diagnostics).push.apply(_b, diagnostics); | ||
this.emit(output); | ||
var oldDecoratorConverter = this.currentDecoratorConverter; | ||
this.currentDecoratorConverter = | ||
new DecoratorClassVisitor(this.typeChecker, this, node, []); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
visitClassContentIncludingDecorators(node, this, this.currentDecoratorConverter); | ||
this.currentDecoratorConverter = oldDecoratorConverter; | ||
return true; | ||
@@ -324,9 +370,25 @@ default: | ||
} | ||
var _b; | ||
}; | ||
return DecoratorRewriter; | ||
}(rewriter_1.Rewriter)); | ||
function convertDecorators(typeChecker, sourceFile) { | ||
function visitClassContentIncludingDecorators(classDecl, rewriter, decoratorVisitor) { | ||
if (rewriter.file.text[classDecl.getEnd() - 1] !== '}') { | ||
rewriter.error(classDecl, 'unexpected class terminator'); | ||
return; | ||
} | ||
rewriter.writeNodeFrom(classDecl, classDecl.getStart(), classDecl.getEnd() - 1); | ||
// At this point, we've emitted up through the final child of the class, so all that | ||
// remains is the trailing whitespace and closing curly brace. | ||
// The final character owned by the class node should always be a '}', | ||
// or we somehow got the AST wrong and should report an error. | ||
// (Any whitespace or semicolon following the '}' will be part of the next Node.) | ||
if (decoratorVisitor) { | ||
decoratorVisitor.emitMetadataAsStaticProperties(); | ||
} | ||
rewriter.writeRange(classDecl, classDecl.getEnd() - 1, classDecl.getEnd()); | ||
} | ||
exports.visitClassContentIncludingDecorators = visitClassContentIncludingDecorators; | ||
function convertDecorators(typeChecker, sourceFile, sourceMapper) { | ||
type_translator_1.assertTypeChecked(sourceFile); | ||
return new DecoratorRewriter(typeChecker, sourceFile).process(); | ||
return new DecoratorRewriter(typeChecker, sourceFile, sourceMapper).process(); | ||
} | ||
@@ -333,0 +395,0 @@ exports.convertDecorators = convertDecorators; |
@@ -22,2 +22,3 @@ "use strict"; | ||
var rewriter_1 = require("./rewriter"); | ||
var tsickle_1 = require("./tsickle"); | ||
var util_1 = require("./util"); | ||
@@ -41,6 +42,6 @@ /** | ||
__extends(ES5Processor, _super); | ||
function ES5Processor(file, pathToModuleName, prelude) { | ||
function ES5Processor(host, options, file) { | ||
var _this = _super.call(this, file) || this; | ||
_this.pathToModuleName = pathToModuleName; | ||
_this.prelude = prelude; | ||
_this.host = host; | ||
_this.options = options; | ||
/** | ||
@@ -69,10 +70,11 @@ * namespaceImports collects the variables for imported goog.modules. | ||
} | ||
ES5Processor.prototype.process = function (moduleId, isES5) { | ||
ES5Processor.prototype.process = function () { | ||
var moduleId = this.host.fileNameToModuleId(this.file.fileName); | ||
// TODO(evanm): only emit the goog.module *after* the first comment, | ||
// so that @suppress statements work. | ||
var moduleName = this.pathToModuleName('', this.file.fileName); | ||
var moduleName = this.host.pathToModuleName('', this.file.fileName); | ||
// NB: No linebreak after module call so sourcemaps are not offset. | ||
this.emit("goog.module('" + moduleName + "');"); | ||
if (this.prelude) | ||
this.emit(this.prelude); | ||
if (this.options.prelude) | ||
this.emit(this.options.prelude); | ||
// Allow code to use `module.id` to discover its module URL, e.g. to resolve | ||
@@ -83,3 +85,3 @@ // a template URL against. | ||
// optimizations mode. | ||
if (isES5) { | ||
if (this.options.es5Mode) { | ||
this.emit("var module = module || {id: '" + moduleId + "'};"); | ||
@@ -98,7 +100,7 @@ } | ||
var stmt = _a[_i]; | ||
this.writeRange(pos, stmt.getFullStart()); | ||
this.writeRange(this.file, pos, stmt.getFullStart()); | ||
this.visitTopLevel(stmt); | ||
pos = stmt.getEnd(); | ||
} | ||
this.writeRange(pos, this.file.getEnd()); | ||
this.writeRange(this.file, pos, this.file.getEnd()); | ||
var referencedModules = util_1.toArray(this.moduleVariables.keys()); | ||
@@ -155,3 +157,3 @@ // Note: don't sort referencedModules, as the keys are in the same order | ||
ES5Processor.prototype.emitCommentWithoutStatementBody = function (node) { | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
}; | ||
@@ -195,3 +197,3 @@ /** isUseStrict returns true if node is a "use strict"; statement. */ | ||
return false; | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
this.emitGoogRequire(varName, require_1); | ||
@@ -223,3 +225,3 @@ return true; | ||
return false; | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
var varName = this.emitGoogRequire(null, require_2); | ||
@@ -260,3 +262,3 @@ if (isExport) { | ||
else { | ||
modName = this.pathToModuleName(this.file.fileName, tsImport); | ||
modName = this.host.pathToModuleName(this.file.fileName, tsImport); | ||
} | ||
@@ -347,3 +349,3 @@ if (!varName) { | ||
// so that source maps still line up. | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
this.emit(lhs + " "); | ||
@@ -376,10 +378,22 @@ return true; | ||
*/ | ||
function processES5(fileName, moduleId, content, pathToModuleName, isES5, prelude) { | ||
if (isES5 === void 0) { isES5 = true; } | ||
if (prelude === void 0) { prelude = ''; } | ||
function processES5(host, options, fileName, content) { | ||
var file = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES5, true); | ||
return new ES5Processor(file, pathToModuleName, prelude).process(moduleId, isES5); | ||
return new ES5Processor(host, options, file).process(); | ||
} | ||
exports.processES5 = processES5; | ||
function convertCommonJsToGoogModuleIfNeeded(host, options, modulesManifest, fileName, content) { | ||
if (!options.googmodule || tsickle_1.isDtsFileName(fileName)) { | ||
return content; | ||
} | ||
var _a = processES5(host, options, fileName, content), output = _a.output, referencedModules = _a.referencedModules; | ||
var moduleName = host.pathToModuleName('', fileName); | ||
modulesManifest.addModule(fileName, moduleName); | ||
for (var _i = 0, referencedModules_1 = referencedModules; _i < referencedModules_1.length; _i++) { | ||
var referenced = referencedModules_1[_i]; | ||
modulesManifest.addReferencedModule(fileName, referenced); | ||
} | ||
return output; | ||
} | ||
exports.convertCommonJsToGoogModuleIfNeeded = convertCommonJsToGoogModuleIfNeeded; | ||
//# sourceMappingURL=es5processor.js.map |
@@ -10,2 +10,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var util_1 = require("./util"); | ||
/** | ||
@@ -89,3 +90,3 @@ * A list of all JSDoc tags allowed by the Closure compiler. | ||
// Make sure we have proper line endings before parsing on Windows. | ||
comment = comment.replace(/\r\n/g, '\n'); | ||
comment = util_1.normalizeLineEndings(comment); | ||
// TODO(evanm): this is a pile of hacky regexes for now, because we | ||
@@ -223,4 +224,5 @@ // would rather use the better TypeScript implementation of JSDoc | ||
var tag = tags[0]; | ||
if (tag.tagName === 'type' && (!tag.text || !tag.text.match('\n'))) { | ||
// Special-case one-liner "type" tags to fit on one line, e.g. | ||
if ((tag.tagName === 'type' || tag.tagName === 'nocollapse') && | ||
(!tag.text || !tag.text.match('\n'))) { | ||
// Special-case one-liner "type" and "nocollapse" tags to fit on one line, e.g. | ||
// /** @type {foo} */ | ||
@@ -227,0 +229,0 @@ return '/**' + tagToString(tag, escapeExtraTags) + ' */\n'; |
@@ -73,2 +73,3 @@ #!/usr/bin/env node | ||
// https://github.com/Microsoft/TypeScript/issues/2620 | ||
// tslint:disable-next-line:no-any | ||
var _a = ts.parseCommandLine(args), options = _a.options, fileNames = _a.fileNames, errors = _a.errors; | ||
@@ -75,0 +76,0 @@ if (errors.length > 0) { |
@@ -18,2 +18,6 @@ "use strict"; | ||
} | ||
ModulesManifest.prototype.addManifest = function (other) { | ||
Object.assign(this.moduleToFileName, other.moduleToFileName); | ||
Object.assign(this.referencedModules, other.referencedModules); | ||
}; | ||
ModulesManifest.prototype.addModule = function (fileName, module) { | ||
@@ -20,0 +24,0 @@ this.moduleToFileName[module] = fileName; |
@@ -10,4 +10,4 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var source_map_1 = require("source-map"); | ||
var ts = require("typescript"); | ||
var source_map_utils_1 = require("./source_map_utils"); | ||
/** | ||
@@ -19,4 +19,6 @@ * A Rewriter manages iterating through a ts.SourceFile, copying input | ||
var Rewriter = (function () { | ||
function Rewriter(file) { | ||
function Rewriter(file, sourceMapper) { | ||
if (sourceMapper === void 0) { sourceMapper = source_map_utils_1.NOOP_SOURCE_MAPPER; } | ||
this.file = file; | ||
this.sourceMapper = sourceMapper; | ||
this.output = []; | ||
@@ -26,3 +28,3 @@ /** Errors found while examining the code. */ | ||
/** Current position in the output. */ | ||
this.position = { line: 1, column: 1 }; | ||
this.position = { line: 0, column: 0, position: 0 }; | ||
/** | ||
@@ -34,12 +36,6 @@ * The current level of recursion through TypeScript Nodes. Used in formatting internal debug | ||
/** | ||
* Skip emitting any code before the given offset. Used to avoid emitting @fileoverview comments | ||
* twice. | ||
* Skip emitting any code before the given offset. E.g. used to avoid emitting @fileoverview | ||
* comments twice. | ||
*/ | ||
this.skipUpToOffset = 0; | ||
this.sourceMap = new source_map_1.SourceMapGenerator({ file: file.fileName }); | ||
this.sourceMap.addMapping({ | ||
original: this.position, | ||
generated: this.position, | ||
source: file.fileName, | ||
}); | ||
this.skipUpToOffset = -1; | ||
} | ||
@@ -53,3 +49,2 @@ Rewriter.prototype.getOutput = function () { | ||
diagnostics: this.diagnostics, | ||
sourceMap: this.sourceMap, | ||
}; | ||
@@ -78,5 +73,5 @@ }; | ||
/** writeNode writes a ts.Node, calling this.visit() on its children. */ | ||
Rewriter.prototype.writeNode = function (node, skipComments) { | ||
var _this = this; | ||
Rewriter.prototype.writeNode = function (node, skipComments, newLineIfCommentsStripped) { | ||
if (skipComments === void 0) { skipComments = false; } | ||
if (newLineIfCommentsStripped === void 0) { newLineIfCommentsStripped = true; } | ||
var pos = node.getFullStart(); | ||
@@ -89,3 +84,3 @@ if (skipComments) { | ||
// there wasn't any comment. | ||
if (node.getFullStart() < node.getStart()) { | ||
if (newLineIfCommentsStripped && node.getFullStart() < node.getStart()) { | ||
this.emit('\n'); | ||
@@ -95,8 +90,19 @@ } | ||
} | ||
this.writeNodeFrom(node, pos); | ||
}; | ||
Rewriter.prototype.writeNodeFrom = function (node, pos, end) { | ||
var _this = this; | ||
if (end === void 0) { end = node.getEnd(); } | ||
if (end <= this.skipUpToOffset) { | ||
return; | ||
} | ||
var oldSkipUpToOffset = this.skipUpToOffset; | ||
this.skipUpToOffset = Math.max(this.skipUpToOffset, pos); | ||
ts.forEachChild(node, function (child) { | ||
_this.writeRange(pos, child.getFullStart()); | ||
_this.writeRange(node, pos, child.getFullStart()); | ||
_this.visit(child); | ||
pos = child.getEnd(); | ||
}); | ||
this.writeRange(pos, node.getEnd()); | ||
this.writeRange(node, pos, end); | ||
this.skipUpToOffset = oldSkipUpToOffset; | ||
}; | ||
@@ -108,4 +114,15 @@ /** | ||
*/ | ||
Rewriter.prototype.writeRange = function (from, to) { | ||
Rewriter.prototype.writeRange = function (node, from, to) { | ||
from = Math.max(from, this.skipUpToOffset); | ||
if (from !== to && to <= this.skipUpToOffset) { | ||
return; | ||
} | ||
// Add a source mapping. writeRange(from, to) always corresponds to | ||
// original source code, so add a mapping at the current location that | ||
// points back to the location at `from`. The additional code generated | ||
// by tsickle will then be considered part of the last mapped code | ||
// section preceding it. That's arguably incorrect (e.g. for the fake | ||
// methods defining properties), but is good enough for stack traces. | ||
var pos = this.file.getLineAndCharacterOfPosition(from); | ||
this.sourceMapper.addMapping(node, { line: pos.line, column: pos.character, position: from }, this.position, to - from); | ||
// getSourceFile().getText() is wrong here because it has the text of | ||
@@ -117,14 +134,2 @@ // the SourceFile node of the AST, which doesn't contain the comments | ||
if (text) { | ||
// Add a source mapping. writeRange(from, to) always corresponds to | ||
// original source code, so add a mapping at the current location that | ||
// points back to the location at `from`. The additional code generated | ||
// by tsickle will then be considered part of the last mapped code | ||
// section preceding it. That's arguably incorrect (e.g. for the fake | ||
// methods defining properties), but is good enough for stack traces. | ||
var pos = this.file.getLineAndCharacterOfPosition(from); | ||
this.sourceMap.addMapping({ | ||
original: { line: pos.line + 1, column: pos.character + 1 }, | ||
generated: this.position, | ||
source: this.file.fileName, | ||
}); | ||
this.emit(text); | ||
@@ -140,5 +145,6 @@ } | ||
this.position.line++; | ||
this.position.column = 1; | ||
this.position.column = 0; | ||
} | ||
} | ||
this.position.position += str.length; | ||
}; | ||
@@ -145,0 +151,0 @@ /** Removes comment metacharacters from a string, to make it safe to embed in a comment. */ |
@@ -62,2 +62,13 @@ "use strict"; | ||
exports.setInlineSourceMap = setInlineSourceMap; | ||
function parseSourceMap(text, fileName, sourceName) { | ||
var rawSourceMap = JSON.parse(text); | ||
if (sourceName) { | ||
rawSourceMap.sources = [sourceName]; | ||
} | ||
if (fileName) { | ||
rawSourceMap.file = fileName; | ||
} | ||
return rawSourceMap; | ||
} | ||
exports.parseSourceMap = parseSourceMap; | ||
function sourceMapConsumerToGenerator(sourceMapConsumer) { | ||
@@ -85,2 +96,3 @@ return source_map_1.SourceMapGenerator.fromSourceMap(sourceMapConsumer); | ||
function sourceMapTextToConsumer(sourceMapText) { | ||
// tslint:disable-next-line:no-any constructor actually supports text. | ||
var sourceMapJson = sourceMapText; | ||
@@ -91,2 +103,3 @@ return new source_map_1.SourceMapConsumer(sourceMapJson); | ||
function sourceMapTextToGenerator(sourceMapText) { | ||
// tslint:disable-next-line:no-any constructor actually supports text. | ||
var sourceMapJson = sourceMapText; | ||
@@ -96,3 +109,30 @@ return source_map_1.SourceMapGenerator.fromSourceMap(sourceMapTextToConsumer(sourceMapJson)); | ||
exports.sourceMapTextToGenerator = sourceMapTextToGenerator; | ||
exports.NOOP_SOURCE_MAPPER = { | ||
// tslint:disable-next-line:no-empty | ||
addMapping: function () { } | ||
}; | ||
var DefaultSourceMapper = (function () { | ||
function DefaultSourceMapper(fileName) { | ||
this.fileName = fileName; | ||
/** The source map that's generated while rewriting this file. */ | ||
this.sourceMap = new source_map_1.SourceMapGenerator(); | ||
this.sourceMap.addMapping({ | ||
original: { line: 1, column: 1 }, | ||
generated: { line: 1, column: 1 }, | ||
source: this.fileName, | ||
}); | ||
} | ||
DefaultSourceMapper.prototype.addMapping = function (node, original, generated, length) { | ||
if (length > 0) { | ||
this.sourceMap.addMapping({ | ||
original: { line: original.line + 1, column: original.column + 1 }, | ||
generated: { line: generated.line + 1, column: generated.column + 1 }, | ||
source: this.fileName, | ||
}); | ||
} | ||
}; | ||
return DefaultSourceMapper; | ||
}()); | ||
exports.DefaultSourceMapper = DefaultSourceMapper; | ||
//# sourceMappingURL=source_map_utils.js.map |
@@ -29,4 +29,13 @@ "use strict"; | ||
Pass[Pass["NONE"] = 0] = "NONE"; | ||
/** | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
Pass[Pass["DECORATOR_DOWNLEVEL"] = 1] = "DECORATOR_DOWNLEVEL"; | ||
/** | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
Pass[Pass["CLOSURIZE"] = 2] = "CLOSURIZE"; | ||
Pass[Pass["DECORATOR_DOWNLEVEL_AND_CLOSURIZE"] = 3] = "DECORATOR_DOWNLEVEL_AND_CLOSURIZE"; | ||
})(Pass = exports.Pass || (exports.Pass = {})); | ||
@@ -54,2 +63,5 @@ /** | ||
this.tsickleSourceMaps = new Map(); | ||
if (options.logWarning && !environment.logWarning) { | ||
environment.logWarning = options.logWarning; | ||
} | ||
// ts.CompilerHost includes a bunch of optional methods. If they're | ||
@@ -94,2 +106,3 @@ // present on the delegate host, we want to delegate them. | ||
this.runConfiguration = { oldProgram: oldProgram, pass: pass }; | ||
this.diagnostics = []; | ||
}; | ||
@@ -106,3 +119,7 @@ TsickleCompilerHost.prototype.getSourceFile = function (fileName, languageVersion, onError) { | ||
case Pass.CLOSURIZE: | ||
return this.closurize(sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion); | ||
return this.closurize(sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion, | ||
/* downlevelDecorators */ false); | ||
case Pass.DECORATOR_DOWNLEVEL_AND_CLOSURIZE: | ||
return this.closurize(sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion, | ||
/* downlevelDecorators */ true); | ||
default: | ||
@@ -117,5 +134,3 @@ throw new Error('tried to use TsickleCompilerHost with unknown pass enum'); | ||
} | ||
if (this.options.googmodule && !tsickle_1.isDtsFileName(fileName)) { | ||
content = this.convertCommonJsToGoogModule(fileName, content); | ||
} | ||
content = this.convertCommonJsToGoogModule(fileName, content); | ||
} | ||
@@ -151,2 +166,4 @@ else { | ||
var _this = this; | ||
var printDebugInfo = false; | ||
// const printDebugInfo = true; | ||
// We stripe inline source maps off source files before they've been parsed | ||
@@ -164,4 +181,9 @@ // which is before they have path properties, so we need to construct the | ||
var tscSourceMapGenerator = sourceMapUtils.sourceMapConsumerToGenerator(tscSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error("tsc source map for " + filePath); | ||
console.error(tscSourceMapGenerator.toString()); | ||
} | ||
if (this.tsickleSourceMaps.size > 0) { | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (var _i = 0, _a = tscSourceMapConsumer.sources; _i < _a.length; _i++) { | ||
@@ -173,2 +195,6 @@ var sourceFileName = _a[_i]; | ||
tscSourceMapGenerator.applySourceMap(tsickleSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error("tsickle source map for " + filePath); | ||
console.error(tsickleSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -178,2 +204,3 @@ } | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (var _b = 0, _c = tscSourceMapConsumer.sources; _b < _c.length; _b++) { | ||
@@ -185,2 +212,6 @@ var sourceFileName = _c[_b]; | ||
tscSourceMapGenerator.applySourceMap(decoratorDownlevelSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error("decorator downlevel sourcemap for " + filePath); | ||
console.error(decoratorDownlevelSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -190,2 +221,3 @@ } | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (var _d = 0, _e = tscSourceMapConsumer.sources; _d < _e.length; _d++) { | ||
@@ -198,2 +230,6 @@ var sourceFileName = _e[_d]; | ||
tscSourceMapGenerator.applySourceMap(preexistingSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error("preexisting source map for " + filePath); | ||
console.error(preexistingSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -210,11 +246,3 @@ } | ||
TsickleCompilerHost.prototype.convertCommonJsToGoogModule = function (fileName, content) { | ||
var moduleId = this.environment.fileNameToModuleId(fileName); | ||
var _a = es5processor_1.processES5(fileName, moduleId, content, this.environment.pathToModuleName.bind(this.environment), this.options.es5Mode, this.options.prelude), output = _a.output, referencedModules = _a.referencedModules; | ||
var moduleName = this.environment.pathToModuleName('', fileName); | ||
this.modulesManifest.addModule(fileName, moduleName); | ||
for (var _i = 0, referencedModules_1 = referencedModules; _i < referencedModules_1.length; _i++) { | ||
var referenced = referencedModules_1[_i]; | ||
this.modulesManifest.addReferencedModule(fileName, referenced); | ||
} | ||
return output; | ||
return es5processor_1.convertCommonJsToGoogModuleIfNeeded(this.environment, this.options, this.modulesManifest, fileName, content); | ||
}; | ||
@@ -226,3 +254,4 @@ TsickleCompilerHost.prototype.downlevelDecorators = function (sourceFile, program, fileName, languageVersion) { | ||
var fileContent = sourceFile.text; | ||
var converted = decorator_annotator_1.convertDecorators(program.getTypeChecker(), sourceFile); | ||
var sourceMapper = new sourceMapUtils.DefaultSourceMapper(sourceFile.fileName); | ||
var converted = decorator_annotator_1.convertDecorators(program.getTypeChecker(), sourceFile, sourceMapper); | ||
if (converted.diagnostics) { | ||
@@ -236,7 +265,7 @@ (_a = this.diagnostics).push.apply(_a, converted.diagnostics); | ||
fileContent = converted.output; | ||
this.decoratorDownlevelSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), converted.sourceMap); | ||
this.decoratorDownlevelSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), sourceMapper.sourceMap); | ||
return ts.createSourceFile(fileName, fileContent, languageVersion, true); | ||
var _a; | ||
}; | ||
TsickleCompilerHost.prototype.closurize = function (sourceFile, program, fileName, languageVersion) { | ||
TsickleCompilerHost.prototype.closurize = function (sourceFile, program, fileName, languageVersion, downlevelDecorators) { | ||
this.tsickleSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), new source_map_1.SourceMapGenerator()); | ||
@@ -248,5 +277,9 @@ var isDefinitions = tsickle_1.isDtsFileName(fileName); | ||
return sourceFile; | ||
var _a = tsickle.annotate(program, sourceFile, this.environment.pathToModuleName.bind(this.environment), this.options, this.delegate, this.tscOptions), output = _a.output, externs = _a.externs, diagnostics = _a.diagnostics, sourceMap = _a.sourceMap; | ||
var sourceMapper = new sourceMapUtils.DefaultSourceMapper(sourceFile.fileName); | ||
var annotated = tsickle.annotate(program.getTypeChecker(), sourceFile, this.environment, this.options, this.delegate, this.tscOptions, sourceMapper, downlevelDecorators ? tsickle.AnnotatorFeatures.LowerDecorators : | ||
tsickle.AnnotatorFeatures.Default); | ||
var externs = tsickle.writeExterns(program.getTypeChecker(), sourceFile, this.environment, this.options); | ||
var diagnostics = externs.diagnostics.concat(annotated.diagnostics); | ||
if (externs) { | ||
this.externs[fileName] = externs; | ||
this.externs[fileName] = externs.output; | ||
} | ||
@@ -260,15 +293,10 @@ if (this.environment.shouldIgnoreWarningsForPath(sourceFile.fileName)) { | ||
} | ||
this.diagnostics = diagnostics; | ||
this.tsickleSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), sourceMap); | ||
return ts.createSourceFile(fileName, output, languageVersion, true); | ||
(_a = this.diagnostics).push.apply(_a, diagnostics); | ||
this.tsickleSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), sourceMapper.sourceMap); | ||
return ts.createSourceFile(fileName, annotated.output, languageVersion, true); | ||
var _a; | ||
}; | ||
/** Concatenate all generated externs definitions together into a string. */ | ||
TsickleCompilerHost.prototype.getGeneratedExterns = function () { | ||
var allExterns = tsickle.EXTERNS_HEADER; | ||
for (var _i = 0, _a = Object.keys(this.externs); _i < _a.length; _i++) { | ||
var fileName = _a[_i]; | ||
allExterns += "// externs from " + fileName + ":\n"; | ||
allExterns += this.externs[fileName]; | ||
} | ||
return allExterns; | ||
return tsickle.getGeneratedExterns(this.externs); | ||
}; | ||
@@ -282,3 +310,2 @@ // Delegate everything else to the original compiler host. | ||
}; | ||
; | ||
TsickleCompilerHost.prototype.useCaseSensitiveFileNames = function () { | ||
@@ -285,0 +312,0 @@ return this.delegate.useCaseSensitiveFileNames(); |
@@ -166,3 +166,7 @@ "use strict"; | ||
var str = ''; | ||
var alias = this.symbolsToAliasedNames.get(sym); | ||
var symAlias = sym; | ||
if (symAlias.flags & ts.SymbolFlags.Alias) { | ||
symAlias = this.typeChecker.getAliasedSymbol(symAlias); | ||
} | ||
var alias = this.symbolsToAliasedNames.get(symAlias); | ||
if (alias) | ||
@@ -373,7 +377,7 @@ return alias; | ||
} | ||
if (type.symbol.flags === ts.SymbolFlags.TypeLiteral) { | ||
if (type.symbol.flags & ts.SymbolFlags.TypeLiteral) { | ||
return this.translateTypeLiteral(type); | ||
} | ||
else if (type.symbol.flags === ts.SymbolFlags.Function || | ||
type.symbol.flags === ts.SymbolFlags.Method) { | ||
else if (type.symbol.flags & ts.SymbolFlags.Function || | ||
type.symbol.flags & ts.SymbolFlags.Method) { | ||
var sigs = this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); | ||
@@ -452,2 +456,3 @@ if (sigs.length === 1) { | ||
fields.push(field + ": " + memberType); | ||
break; | ||
} | ||
@@ -517,6 +522,5 @@ } | ||
var pathBlackList = this.pathBlackList; | ||
if (symbol.declarations === undefined) { | ||
this.warn('symbol has no declarations'); | ||
return true; | ||
} | ||
// Some builtin types, such as {}, get represented by a symbol that has no declarations. | ||
if (symbol.declarations === undefined) | ||
return false; | ||
return symbol.declarations.every(function (n) { | ||
@@ -523,0 +527,0 @@ var fileName = path.normalize(n.getSourceFile().fileName); |
@@ -83,3 +83,10 @@ "use strict"; | ||
exports.createOutputRetainingCompilerHost = createOutputRetainingCompilerHost; | ||
/** | ||
* Returns the input string with line endings normalized to '\n'. | ||
*/ | ||
function normalizeLineEndings(input) { | ||
return input.replace(/\r\n/g, '\n'); | ||
} | ||
exports.normalizeLineEndings = normalizeLineEndings; | ||
//# sourceMappingURL=util.js.map |
{ | ||
"name": "tsickle", | ||
"version": "0.23.3", | ||
"version": "0.23.4", | ||
"description": "Transpile TypeScript code to JavaScript with Closure annotations.", | ||
@@ -18,6 +18,7 @@ "main": "built/src/tsickle.js", | ||
"peerDependencies": { | ||
"typescript": "2.3.1" | ||
"typescript": "2.3.4" | ||
}, | ||
"devDependencies": { | ||
"@types/chai": "^3.4.32", | ||
"@types/diff": "^3.2.0", | ||
"@types/glob": "^5.0.29", | ||
@@ -34,2 +35,3 @@ "@types/google-closure-compiler": "0.0.18", | ||
"clang-format": "^1.0.51", | ||
"diff": "^3.2.0", | ||
"glob": "^7.0.0", | ||
@@ -41,3 +43,3 @@ "google-closure-compiler": "^20161024.1.0", | ||
"gulp-sourcemaps": "^1.5.0", | ||
"gulp-tslint": "^6.1.1", | ||
"gulp-tslint": "^8.1.0", | ||
"gulp-typescript": "^3.0.0", | ||
@@ -48,4 +50,4 @@ "gulp-util": "^3.0.4", | ||
"temp": "^0.8.1", | ||
"tslint": "^3.15.1", | ||
"typescript": "~2.3.1" | ||
"tslint": "^5.4.2", | ||
"typescript": "~2.3.4" | ||
}, | ||
@@ -76,2 +78,2 @@ "scripts": { | ||
"homepage": "https://github.com/angular/tsickle" | ||
} | ||
} |
@@ -21,3 +21,3 @@ /** | ||
// Replace characters not supported by goog.module. | ||
let moduleName = | ||
const moduleName = | ||
fileName.replace(/\/|\\/g, '.').replace(/^[^a-zA-Z_$]/, '_').replace(/[^a-zA-Z0-9._$]/g, '_'); | ||
@@ -24,0 +24,0 @@ |
@@ -13,19 +13,26 @@ /** | ||
import {getDecoratorDeclarations} from './decorators'; | ||
import {Rewriter} from './rewriter'; | ||
import {getIdentifierText, Rewriter} from './rewriter'; | ||
import {SourceMapper} from './source_map_utils'; | ||
import {assertTypeChecked, TypeTranslator} from './type-translator'; | ||
import {toArray} from './util'; | ||
// ClassRewriter rewrites a single "class Foo {...}" declaration. | ||
// DecoratorClassVisitor rewrites a single "class Foo {...}" declaration. | ||
// It's its own object because we collect decorators on the class and the ctor | ||
// separately for each class we encounter. | ||
class ClassRewriter extends Rewriter { | ||
export class DecoratorClassVisitor { | ||
/** Decorators on the class itself. */ | ||
decorators: ts.Decorator[]; | ||
/** The constructor parameter list and decorators on each param. */ | ||
ctorParameters: ([string | undefined, ts.Decorator[]|undefined]|null)[]; | ||
ctorParameters: Array<[[ts.Symbol, string] | undefined, ts.Decorator[]|undefined]|null>; | ||
/** Per-method decorators. */ | ||
propDecorators: Map<string, ts.Decorator[]>; | ||
constructor(private typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile) { | ||
super(sourceFile); | ||
constructor( | ||
private typeChecker: ts.TypeChecker, private rewriter: Rewriter, | ||
private classDecl: ts.ClassDeclaration, | ||
private importedNames: Array<{name: ts.Identifier, declarationNames: ts.Identifier[]}>) { | ||
if (classDecl.decorators) { | ||
const toLower = this.decoratorsToLower(classDecl); | ||
if (toLower.length > 0) this.decorators = toLower; | ||
} | ||
} | ||
@@ -37,3 +44,3 @@ | ||
private shouldLower(decorator: ts.Decorator) { | ||
for (let d of getDecoratorDeclarations(decorator, this.typeChecker)) { | ||
for (const d of getDecoratorDeclarations(decorator, this.typeChecker)) { | ||
// Switch to the TS JSDoc parser in the future to avoid false positives here. | ||
@@ -57,6 +64,6 @@ // For example using '@Annotation' in a true comment. | ||
} | ||
let range = ts.getLeadingCommentRanges(commentNode.getFullText(), 0); | ||
const range = ts.getLeadingCommentRanges(commentNode.getFullText(), 0); | ||
if (!range) continue; | ||
for (let {pos, end} of range) { | ||
let jsDocText = commentNode.getFullText().substring(pos, end); | ||
for (const {pos, end} of range) { | ||
const jsDocText = commentNode.getFullText().substring(pos, end); | ||
if (jsDocText.includes('@Annotation')) return true; | ||
@@ -76,36 +83,2 @@ } | ||
/** | ||
* process is the main entry point, rewriting a single class node. | ||
*/ | ||
process(node: ts.ClassDeclaration): {output: string, diagnostics: ts.Diagnostic[]} { | ||
if (node.decorators) { | ||
let toLower = this.decoratorsToLower(node); | ||
if (toLower.length > 0) this.decorators = toLower; | ||
} | ||
// Emit the class contents, but stop just before emitting the closing curly brace. | ||
// (This code is the same as Rewriter.writeNode except for the curly brace handling.) | ||
let pos = node.getFullStart(); | ||
ts.forEachChild(node, child => { | ||
// This forEachChild handles emitting the text between each child, while child.visit | ||
// recursively emits the children themselves. | ||
this.writeRange(pos, child.getFullStart()); | ||
this.visit(child); | ||
pos = child.getEnd(); | ||
}); | ||
// At this point, we've emitted up through the final child of the class, so all that | ||
// remains is the trailing whitespace and closing curly brace. | ||
// The final character owned by the class node should always be a '}', | ||
// or we somehow got the AST wrong and should report an error. | ||
// (Any whitespace or semicolon following the '}' will be part of the next Node.) | ||
if (this.file.text[node.getEnd() - 1] !== '}') { | ||
this.error(node, 'unexpected class terminator'); | ||
} | ||
this.writeRange(pos, node.getEnd() - 1); | ||
this.emitMetadata(); | ||
this.emit('}'); | ||
return this.getOutput(); | ||
} | ||
/** | ||
* gatherConstructor grabs the parameter list and decorators off the class | ||
@@ -115,6 +88,7 @@ * constructor, and emits nothing. | ||
private gatherConstructor(ctor: ts.ConstructorDeclaration) { | ||
let ctorParameters: ([string | undefined, ts.Decorator[] | undefined]|null)[] = []; | ||
const ctorParameters: | ||
Array<[[ts.Symbol, string] | undefined, ts.Decorator[] | undefined]|null> = []; | ||
let hasDecoratedParam = false; | ||
for (let param of ctor.parameters) { | ||
let paramCtor: string|undefined; | ||
for (const param of ctor.parameters) { | ||
let paramCtor: [ts.Symbol, string]|undefined; | ||
let decorators: ts.Decorator[]|undefined; | ||
@@ -128,6 +102,7 @@ if (param.decorators) { | ||
// Verify that "Bar" is a value (e.g. a constructor) and not just a type. | ||
let sym = this.typeChecker.getTypeAtLocation(param.type).getSymbol(); | ||
const sym = this.typeChecker.getTypeAtLocation(param.type).getSymbol(); | ||
if (sym && (sym.flags & ts.SymbolFlags.Value)) { | ||
paramCtor = new TypeTranslator(this.typeChecker, param.type) | ||
.symbolToString(sym, /* useFqn */ true); | ||
const typeStr = new TypeTranslator(this.typeChecker, param.type) | ||
.symbolToString(sym, /* useFqn */ true); | ||
paramCtor = [sym, typeStr]; | ||
} | ||
@@ -156,8 +131,8 @@ } | ||
// [Symbol.foo]() {...} | ||
this.error(method, 'cannot process decorators on strangely named method'); | ||
this.rewriter.error(method, 'cannot process decorators on strangely named method'); | ||
return; | ||
} | ||
let name = (method.name as ts.Identifier).text; | ||
let decorators: ts.Decorator[] = this.decoratorsToLower(method); | ||
const name = (method.name as ts.Identifier).text; | ||
const decorators: ts.Decorator[] = this.decoratorsToLower(method); | ||
if (decorators.length === 0) return; | ||
@@ -169,18 +144,38 @@ if (!this.propDecorators) this.propDecorators = new Map<string, ts.Decorator[]>(); | ||
/** | ||
* maybeProcess is called by the traversal of the AST. | ||
* @return True if the node was handled, false to have the node emitted as normal. | ||
* For lowering decorators, we need to refer to constructor types. | ||
* So we start with the identifiers that represent these types. | ||
* However, TypeScript does not allow use to emit them in a value position | ||
* as it associated different symbol information with it. | ||
* | ||
* This method looks for the place where the value that is associated to | ||
* the type is defined and returns that identifier instead. | ||
* | ||
* @param typeSymbol | ||
* @return The identifier | ||
*/ | ||
protected maybeProcess(node: ts.Node): boolean { | ||
private getValueIdentifierForType(typeSymbol: ts.Symbol): ts.Identifier|null { | ||
if (!typeSymbol.valueDeclaration) { | ||
return null; | ||
} | ||
const valueName = typeSymbol.valueDeclaration.name; | ||
if (!valueName || valueName.kind !== ts.SyntaxKind.Identifier) { | ||
return null; | ||
} | ||
if (valueName.getSourceFile() === this.rewriter.file) { | ||
return valueName; | ||
} | ||
for (let i = 0; i < this.importedNames.length; i++) { | ||
const {name, declarationNames} = this.importedNames[i]; | ||
if (declarationNames.some(d => d === valueName)) { | ||
return name; | ||
} | ||
} | ||
return null; | ||
} | ||
beforeProcessNode(node: ts.Node) { | ||
switch (node.kind) { | ||
case ts.SyntaxKind.ClassDeclaration: | ||
// Encountered a new class while processing this class; use a new separate | ||
// rewriter to gather+emit its metadata. | ||
let {output, diagnostics} = | ||
new ClassRewriter(this.typeChecker, this.file).process(node as ts.ClassDeclaration); | ||
this.diagnostics.push(...diagnostics); | ||
this.emit(output); | ||
return true; | ||
case ts.SyntaxKind.Constructor: | ||
this.gatherConstructor(node as ts.ConstructorDeclaration); | ||
return false; // Proceed with ordinary emit of the ctor. | ||
break; | ||
case ts.SyntaxKind.PropertyDeclaration: | ||
@@ -191,69 +186,115 @@ case ts.SyntaxKind.SetAccessor: | ||
this.gatherMethodOrProperty(node as ts.Declaration); | ||
return false; // Proceed with ordinary emit of the method. | ||
case ts.SyntaxKind.Decorator: | ||
if (this.shouldLower(node as ts.Decorator)) { | ||
// Return true to signal that this node should not be emitted, | ||
// but still emit the whitespace *before* the node. | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
return true; | ||
} | ||
return false; | ||
break; | ||
default: | ||
return false; | ||
} | ||
} | ||
maybeProcessDecorator(node: ts.Decorator, start?: number): boolean { | ||
if (this.shouldLower(node)) { | ||
// Return true to signal that this node should not be emitted, | ||
// but still emit the whitespace *before* the node. | ||
if (!start) { | ||
start = node.getFullStart(); | ||
} | ||
this.rewriter.writeRange(node, start, node.getStart()); | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* emits the types for the various gathered metadata to be used | ||
* in the tsickle type annotations helper. | ||
*/ | ||
emitMetadataTypeAnnotationsHelpers() { | ||
if (!this.classDecl.name) return; | ||
const className = getIdentifierText(this.classDecl.name); | ||
if (this.decorators) { | ||
this.rewriter.emit(`/** @type {!Array<{type: !Function, args: (undefined|!Array<?>)}>} */\n`); | ||
this.rewriter.emit(`${className}.decorators;\n`); | ||
} | ||
if (this.decorators || this.ctorParameters) { | ||
this.rewriter.emit(`/**\n`); | ||
this.rewriter.emit(` * @nocollapse\n`); | ||
this.rewriter.emit( | ||
` * @type {function(): !Array<(null|{type: ?, decorators: (undefined|!Array<{type: !Function, args: (undefined|!Array<?>)}>)})>}\n`); | ||
this.rewriter.emit(` */\n`); | ||
this.rewriter.emit(`${className}.ctorParameters;\n`); | ||
} | ||
if (this.propDecorators) { | ||
this.rewriter.emit( | ||
`/** @type {!Object<string,!Array<{type: !Function, args: (undefined|!Array<?>)}>>} */\n`); | ||
this.rewriter.emit(`${className}.propDecorators;\n`); | ||
} | ||
} | ||
/** | ||
* emitMetadata emits the various gathered metadata, as static fields. | ||
*/ | ||
private emitMetadata() { | ||
emitMetadataAsStaticProperties() { | ||
const decoratorInvocations = '{type: Function, args?: any[]}[]'; | ||
if (this.decorators) { | ||
this.emit(`static decorators: ${decoratorInvocations} = [\n`); | ||
for (let annotation of this.decorators) { | ||
this.rewriter.emit(`static decorators: ${decoratorInvocations} = [\n`); | ||
for (const annotation of this.decorators) { | ||
this.emitDecorator(annotation); | ||
this.emit(',\n'); | ||
this.rewriter.emit(',\n'); | ||
} | ||
this.emit('];\n'); | ||
this.rewriter.emit('];\n'); | ||
} | ||
if (this.decorators || this.ctorParameters) { | ||
this.emit(`/** @nocollapse */\n`); | ||
this.rewriter.emit(`/** @nocollapse */\n`); | ||
// ctorParameters may contain forward references in the type: field, so wrap in a function | ||
// closure | ||
this.emit( | ||
this.rewriter.emit( | ||
`static ctorParameters: () => ({type: any, decorators?: ` + decoratorInvocations + | ||
`}|null)[] = () => [\n`); | ||
for (let param of this.ctorParameters || []) { | ||
for (const param of this.ctorParameters || []) { | ||
if (!param) { | ||
this.emit('null,\n'); | ||
this.rewriter.emit('null,\n'); | ||
continue; | ||
} | ||
let [ctor, decorators] = param; | ||
this.emit(`{type: ${ctor}, `); | ||
const [ctor, decorators] = param; | ||
this.rewriter.emit(`{type: `); | ||
if (!ctor) { | ||
this.rewriter.emit(`undefined`); | ||
} else { | ||
const [typeSymbol, typeStr] = ctor; | ||
let emitNode: ts.Identifier|null|undefined; | ||
if (typeSymbol) { | ||
emitNode = this.getValueIdentifierForType(typeSymbol); | ||
} | ||
if (emitNode) { | ||
this.rewriter.writeRange(emitNode, emitNode.getStart(), emitNode.getEnd()); | ||
} else { | ||
this.rewriter.emit(typeStr); | ||
} | ||
} | ||
this.rewriter.emit(`, `); | ||
if (decorators) { | ||
this.emit('decorators: ['); | ||
for (let decorator of decorators) { | ||
this.rewriter.emit('decorators: ['); | ||
for (const decorator of decorators) { | ||
this.emitDecorator(decorator); | ||
this.emit(', '); | ||
this.rewriter.emit(', '); | ||
} | ||
this.emit(']'); | ||
this.rewriter.emit(']'); | ||
} | ||
this.emit('},\n'); | ||
this.rewriter.emit('},\n'); | ||
} | ||
this.emit(`];\n`); | ||
this.rewriter.emit(`];\n`); | ||
} | ||
if (this.propDecorators) { | ||
this.emit(`static propDecorators: {[key: string]: ` + decoratorInvocations + `} = {\n`); | ||
for (let name of toArray(this.propDecorators.keys())) { | ||
this.emit(`'${name}': [`); | ||
this.rewriter.emit( | ||
`static propDecorators: {[key: string]: ` + decoratorInvocations + `} = {\n`); | ||
for (const name of toArray(this.propDecorators.keys())) { | ||
this.rewriter.emit(`"${name}": [`); | ||
for (let decorator of this.propDecorators.get(name)!) { | ||
for (const decorator of this.propDecorators.get(name)!) { | ||
this.emitDecorator(decorator); | ||
this.emit(','); | ||
this.rewriter.emit(','); | ||
} | ||
this.emit('],\n'); | ||
this.rewriter.emit('],\n'); | ||
} | ||
this.emit('};\n'); | ||
this.rewriter.emit('};\n'); | ||
} | ||
@@ -263,27 +304,27 @@ } | ||
private emitDecorator(decorator: ts.Decorator) { | ||
this.emit('{ type: '); | ||
let expr = decorator.expression; | ||
this.rewriter.emit('{ type: '); | ||
const expr = decorator.expression; | ||
switch (expr.kind) { | ||
case ts.SyntaxKind.Identifier: | ||
// The decorator was a plain @Foo. | ||
this.visit(expr); | ||
this.rewriter.visit(expr); | ||
break; | ||
case ts.SyntaxKind.CallExpression: | ||
// The decorator was a call, like @Foo(bar). | ||
let call = expr as ts.CallExpression; | ||
this.visit(call.expression); | ||
const call = expr as ts.CallExpression; | ||
this.rewriter.visit(call.expression); | ||
if (call.arguments.length) { | ||
this.emit(', args: ['); | ||
for (let arg of call.arguments) { | ||
this.emit(arg.getText()); | ||
this.emit(', '); | ||
this.rewriter.emit(', args: ['); | ||
for (const arg of call.arguments) { | ||
this.rewriter.writeNodeFrom(arg, arg.getStart()); | ||
this.rewriter.emit(', '); | ||
} | ||
this.emit(']'); | ||
this.rewriter.emit(']'); | ||
} | ||
break; | ||
default: | ||
this.errorUnimplementedKind(expr, 'gathering metadata'); | ||
this.emit('undefined'); | ||
this.rewriter.errorUnimplementedKind(expr, 'gathering metadata'); | ||
this.rewriter.emit('undefined'); | ||
} | ||
this.emit(' }'); | ||
this.rewriter.emit(' }'); | ||
} | ||
@@ -293,7 +334,10 @@ } | ||
class DecoratorRewriter extends Rewriter { | ||
constructor(private typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile) { | ||
super(sourceFile); | ||
private currentDecoratorConverter: DecoratorClassVisitor; | ||
constructor( | ||
private typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile, sourceMapper?: SourceMapper) { | ||
super(sourceFile, sourceMapper); | ||
} | ||
process(): {output: string, diagnostics: ts.Diagnostic[], sourceMap: SourceMapGenerator} { | ||
process(): {output: string, diagnostics: ts.Diagnostic[]} { | ||
this.visit(this.file); | ||
@@ -304,8 +348,17 @@ return this.getOutput(); | ||
protected maybeProcess(node: ts.Node): boolean { | ||
if (this.currentDecoratorConverter) { | ||
this.currentDecoratorConverter.beforeProcessNode(node); | ||
} | ||
switch (node.kind) { | ||
case ts.SyntaxKind.Decorator: | ||
return this.currentDecoratorConverter && | ||
this.currentDecoratorConverter.maybeProcessDecorator(node as ts.Decorator); | ||
case ts.SyntaxKind.ClassDeclaration: | ||
let {output, diagnostics} = | ||
new ClassRewriter(this.typeChecker, this.file).process(node as ts.ClassDeclaration); | ||
this.diagnostics.push(...diagnostics); | ||
this.emit(output); | ||
const oldDecoratorConverter = this.currentDecoratorConverter; | ||
this.currentDecoratorConverter = | ||
new DecoratorClassVisitor(this.typeChecker, this, node as ts.ClassDeclaration, []); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
visitClassContentIncludingDecorators( | ||
node as ts.ClassDeclaration, this, this.currentDecoratorConverter); | ||
this.currentDecoratorConverter = oldDecoratorConverter; | ||
return true; | ||
@@ -318,6 +371,26 @@ default: | ||
export function convertDecorators(typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile): | ||
{output: string, diagnostics: ts.Diagnostic[], sourceMap: SourceMapGenerator} { | ||
export function visitClassContentIncludingDecorators( | ||
classDecl: ts.ClassDeclaration, rewriter: Rewriter, decoratorVisitor?: DecoratorClassVisitor) { | ||
if (rewriter.file.text[classDecl.getEnd() - 1] !== '}') { | ||
rewriter.error(classDecl, 'unexpected class terminator'); | ||
return; | ||
} | ||
rewriter.writeNodeFrom(classDecl, classDecl.getStart(), classDecl.getEnd() - 1); | ||
// At this point, we've emitted up through the final child of the class, so all that | ||
// remains is the trailing whitespace and closing curly brace. | ||
// The final character owned by the class node should always be a '}', | ||
// or we somehow got the AST wrong and should report an error. | ||
// (Any whitespace or semicolon following the '}' will be part of the next Node.) | ||
if (decoratorVisitor) { | ||
decoratorVisitor.emitMetadataAsStaticProperties(); | ||
} | ||
rewriter.writeRange(classDecl, classDecl.getEnd() - 1, classDecl.getEnd()); | ||
} | ||
export function convertDecorators( | ||
typeChecker: ts.TypeChecker, sourceFile: ts.SourceFile, | ||
sourceMapper?: SourceMapper): {output: string, diagnostics: ts.Diagnostic[]} { | ||
assertTypeChecked(sourceFile); | ||
return new DecoratorRewriter(typeChecker, sourceFile).process(); | ||
return new DecoratorRewriter(typeChecker, sourceFile, sourceMapper).process(); | ||
} |
@@ -47,7 +47,7 @@ /** | ||
return getDecoratorDeclarations(decorator, typeChecker).some(declaration => { | ||
let range = ts.getLeadingCommentRanges(declaration.getFullText(), 0); | ||
const range = ts.getLeadingCommentRanges(declaration.getFullText(), 0); | ||
if (!range) { | ||
return false; | ||
} | ||
for (let {pos, end} of range) { | ||
for (const {pos, end} of range) { | ||
if (/@ExportDecoratedItems\b/.test(declaration.getFullText().substring(pos, end))) { | ||
@@ -54,0 +54,0 @@ return true; |
@@ -11,5 +11,27 @@ /** | ||
import {ModulesManifest} from './modules_manifest'; | ||
import {getIdentifierText, Rewriter} from './rewriter'; | ||
import {isDtsFileName} from './tsickle'; | ||
import {toArray} from './util'; | ||
export interface Es5ProcessorHost { | ||
/** | ||
* Takes a context (the current file) and the path of the file to import | ||
* and generates a googmodule module name | ||
*/ | ||
pathToModuleName(context: string, importPath: string): string; | ||
/** | ||
* If we do googmodule processing, we polyfill module.id, since that's | ||
* part of ES6 modules. This function determines what the module.id will be | ||
* for each file. | ||
*/ | ||
fileNameToModuleId(fileName: string): string; | ||
} | ||
export interface Es5ProcessorOptions { | ||
googmodule?: boolean; | ||
es5Mode?: boolean; | ||
prelude?: string; | ||
} | ||
/** | ||
@@ -50,20 +72,20 @@ * Extracts the namespace part of a goog: import, or returns null if the given | ||
/** strippedStrict is true once we've stripped a "use strict"; from the input. */ | ||
strippedStrict: boolean = false; | ||
strippedStrict = false; | ||
/** unusedIndex is used to generate fresh symbols for unnamed imports. */ | ||
unusedIndex: number = 0; | ||
unusedIndex = 0; | ||
constructor( | ||
file: ts.SourceFile, private pathToModuleName: (context: string, fileName: string) => string, | ||
private prelude: string) { | ||
private host: Es5ProcessorHost, private options: Es5ProcessorOptions, file: ts.SourceFile) { | ||
super(file); | ||
} | ||
process(moduleId: string, isES5: boolean): {output: string, referencedModules: string[]} { | ||
process(): {output: string, referencedModules: string[]} { | ||
const moduleId = this.host.fileNameToModuleId(this.file.fileName); | ||
// TODO(evanm): only emit the goog.module *after* the first comment, | ||
// so that @suppress statements work. | ||
const moduleName = this.pathToModuleName('', this.file.fileName); | ||
const moduleName = this.host.pathToModuleName('', this.file.fileName); | ||
// NB: No linebreak after module call so sourcemaps are not offset. | ||
this.emit(`goog.module('${moduleName}');`); | ||
if (this.prelude) this.emit(this.prelude); | ||
if (this.options.prelude) this.emit(this.options.prelude); | ||
// Allow code to use `module.id` to discover its module URL, e.g. to resolve | ||
@@ -74,3 +96,3 @@ // a template URL against. | ||
// optimizations mode. | ||
if (isES5) { | ||
if (this.options.es5Mode) { | ||
this.emit(`var module = module || {id: '${moduleId}'};`); | ||
@@ -87,13 +109,13 @@ } else { | ||
let pos = 0; | ||
for (let stmt of this.file.statements) { | ||
this.writeRange(pos, stmt.getFullStart()); | ||
for (const stmt of this.file.statements) { | ||
this.writeRange(this.file, pos, stmt.getFullStart()); | ||
this.visitTopLevel(stmt); | ||
pos = stmt.getEnd(); | ||
} | ||
this.writeRange(pos, this.file.getEnd()); | ||
this.writeRange(this.file, pos, this.file.getEnd()); | ||
let referencedModules = toArray(this.moduleVariables.keys()); | ||
const referencedModules = toArray(this.moduleVariables.keys()); | ||
// Note: don't sort referencedModules, as the keys are in the same order | ||
// they occur in the source file. | ||
let {output} = this.getOutput(); | ||
const {output} = this.getOutput(); | ||
return {output, referencedModules}; | ||
@@ -147,3 +169,3 @@ } | ||
emitCommentWithoutStatementBody(node: ts.Node) { | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
} | ||
@@ -154,6 +176,6 @@ | ||
if (node.kind !== ts.SyntaxKind.ExpressionStatement) return false; | ||
let exprStmt = node as ts.ExpressionStatement; | ||
let expr = exprStmt.expression; | ||
const exprStmt = node as ts.ExpressionStatement; | ||
const expr = exprStmt.expression; | ||
if (expr.kind !== ts.SyntaxKind.StringLiteral) return false; | ||
let literal = expr as ts.StringLiteral; | ||
const literal = expr as ts.StringLiteral; | ||
return literal.text === 'use strict'; | ||
@@ -173,16 +195,16 @@ } | ||
// It's possibly of the form "var x = require(...);". | ||
let varStmt = node as ts.VariableStatement; | ||
const varStmt = node as ts.VariableStatement; | ||
// Verify it's a single decl (and not "var x = ..., y = ...;"). | ||
if (varStmt.declarationList.declarations.length !== 1) return false; | ||
let decl = varStmt.declarationList.declarations[0]; | ||
const decl = varStmt.declarationList.declarations[0]; | ||
// Grab the variable name (avoiding things like destructuring binds). | ||
if (decl.name.kind !== ts.SyntaxKind.Identifier) return false; | ||
let varName = getIdentifierText(decl.name as ts.Identifier); | ||
const varName = getIdentifierText(decl.name as ts.Identifier); | ||
if (!decl.initializer || decl.initializer.kind !== ts.SyntaxKind.CallExpression) return false; | ||
let call = decl.initializer as ts.CallExpression; | ||
let require = this.isRequire(call); | ||
const call = decl.initializer as ts.CallExpression; | ||
const require = this.isRequire(call); | ||
if (!require) return false; | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
this.emitGoogRequire(varName, require); | ||
@@ -195,6 +217,6 @@ return true; | ||
// Both are CallExpressions. | ||
let exprStmt = node as ts.ExpressionStatement; | ||
let expr = exprStmt.expression; | ||
const exprStmt = node as ts.ExpressionStatement; | ||
const expr = exprStmt.expression; | ||
if (expr.kind !== ts.SyntaxKind.CallExpression) return false; | ||
let call = expr as ts.CallExpression; | ||
const call = expr as ts.CallExpression; | ||
@@ -214,4 +236,4 @@ let require = this.isRequire(call); | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
let varName = this.emitGoogRequire(null, require); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
const varName = this.emitGoogRequire(null, require); | ||
@@ -251,7 +273,7 @@ if (isExport) { | ||
} else { | ||
modName = this.pathToModuleName(this.file.fileName, tsImport); | ||
modName = this.host.pathToModuleName(this.file.fileName, tsImport); | ||
} | ||
if (!varName) { | ||
let mv = this.moduleVariables.get(modName); | ||
const mv = this.moduleVariables.get(modName); | ||
if (mv) { | ||
@@ -286,3 +308,3 @@ // Caller didn't request a specific variable name and we've already | ||
if (call.expression.kind !== ts.SyntaxKind.Identifier) return null; | ||
let ident = call.expression as ts.Identifier; | ||
const ident = call.expression as ts.Identifier; | ||
if (getIdentifierText(ident) !== 'require') return null; | ||
@@ -292,3 +314,3 @@ | ||
if (call.arguments.length !== 1) return null; | ||
let arg = call.arguments[0]; | ||
const arg = call.arguments[0]; | ||
if (arg.kind !== ts.SyntaxKind.StringLiteral) return null; | ||
@@ -304,3 +326,3 @@ return (arg as ts.StringLiteral).text; | ||
if (call.expression.kind !== ts.SyntaxKind.Identifier) return null; | ||
let ident = call.expression as ts.Identifier; | ||
const ident = call.expression as ts.Identifier; | ||
if (ident.getText() !== '__export') return null; | ||
@@ -310,3 +332,3 @@ | ||
if (call.arguments.length !== 1) return null; | ||
let arg = call.arguments[0]; | ||
const arg = call.arguments[0]; | ||
if (arg.kind !== ts.SyntaxKind.CallExpression) return null; | ||
@@ -329,3 +351,3 @@ return this.isRequire(arg as ts.CallExpression); | ||
case ts.SyntaxKind.PropertyAccessExpression: | ||
let propAccess = node as ts.PropertyAccessExpression; | ||
const propAccess = node as ts.PropertyAccessExpression; | ||
// We're looking for an expression of the form: | ||
@@ -335,7 +357,7 @@ // module_name_var.default | ||
if (propAccess.expression.kind !== ts.SyntaxKind.Identifier) break; | ||
let lhs = getIdentifierText(propAccess.expression as ts.Identifier); | ||
const lhs = getIdentifierText(propAccess.expression as ts.Identifier); | ||
if (!this.namespaceImports.has(lhs)) break; | ||
// Emit the same expression, with spaces to replace the ".default" part | ||
// so that source maps still line up. | ||
this.writeRange(node.getFullStart(), node.getStart()); | ||
this.writeRange(node, node.getFullStart(), node.getStart()); | ||
this.emit(`${lhs} `); | ||
@@ -370,7 +392,23 @@ return true; | ||
export function processES5( | ||
fileName: string, moduleId: string, content: string, | ||
pathToModuleName: (context: string, fileName: string) => string, isES5 = true, | ||
prelude = ''): {output: string, referencedModules: string[]} { | ||
let file = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES5, true); | ||
return new ES5Processor(file, pathToModuleName, prelude).process(moduleId, isES5); | ||
host: Es5ProcessorHost, options: Es5ProcessorOptions, fileName: string, | ||
content: string): {output: string, referencedModules: string[]} { | ||
const file = ts.createSourceFile(fileName, content, ts.ScriptTarget.ES5, true); | ||
return new ES5Processor(host, options, file).process(); | ||
} | ||
export function convertCommonJsToGoogModuleIfNeeded( | ||
host: Es5ProcessorHost, options: Es5ProcessorOptions, modulesManifest: ModulesManifest, | ||
fileName: string, content: string): string { | ||
if (!options.googmodule || isDtsFileName(fileName)) { | ||
return content; | ||
} | ||
const {output, referencedModules} = processES5(host, options, fileName, content); | ||
const moduleName = host.pathToModuleName('', fileName); | ||
modulesManifest.addModule(fileName, moduleName); | ||
for (const referenced of referencedModules) { | ||
modulesManifest.addReferencedModule(fileName, referenced); | ||
} | ||
return output; | ||
} |
@@ -9,2 +9,4 @@ /** | ||
import {normalizeLineEndings} from './util'; | ||
/** | ||
@@ -127,3 +129,3 @@ * TypeScript has an API for JSDoc already, but it's not exposed. | ||
// Make sure we have proper line endings before parsing on Windows. | ||
comment = comment.replace(/\r\n/g, '\n'); | ||
comment = normalizeLineEndings(comment); | ||
// TODO(evanm): this is a pile of hacky regexes for now, because we | ||
@@ -137,6 +139,6 @@ // would rather use the better TypeScript implementation of JSDoc | ||
comment = comment.replace(/^\s*\*? ?/gm, ''); | ||
let lines = comment.split('\n'); | ||
let tags: Tag[] = []; | ||
let warnings: string[] = []; | ||
for (let line of lines) { | ||
const lines = comment.split('\n'); | ||
const tags: Tag[] = []; | ||
const warnings: string[] = []; | ||
for (const line of lines) { | ||
match = line.match(/^@(\S+) *(.*)/); | ||
@@ -177,3 +179,3 @@ if (match) { | ||
let tag: Tag = {tagName}; | ||
const tag: Tag = {tagName}; | ||
if (parameterName) tag.parameterName = parameterName; | ||
@@ -204,3 +206,3 @@ if (text) tag.text = text; | ||
*/ | ||
function tagToString(tag: Tag, escapeExtraTags: Set<string> = new Set<string>()): string { | ||
function tagToString(tag: Tag, escapeExtraTags = new Set<string>()): string { | ||
let out = ''; | ||
@@ -249,8 +251,9 @@ if (tag.tagName) { | ||
/** Serializes a Comment out to a string usable in source code. */ | ||
export function toString(tags: Tag[], escapeExtraTags: Set<string> = new Set<string>()): string { | ||
export function toString(tags: Tag[], escapeExtraTags = new Set<string>()): string { | ||
if (tags.length === 0) return ''; | ||
if (tags.length === 1) { | ||
let tag = tags[0]; | ||
if (tag.tagName === 'type' && (!tag.text || !tag.text.match('\n'))) { | ||
// Special-case one-liner "type" tags to fit on one line, e.g. | ||
const tag = tags[0]; | ||
if ((tag.tagName === 'type' || tag.tagName === 'nocollapse') && | ||
(!tag.text || !tag.text.match('\n'))) { | ||
// Special-case one-liner "type" and "nocollapse" tags to fit on one line, e.g. | ||
// /** @type {foo} */ | ||
@@ -265,3 +268,3 @@ return '/**' + tagToString(tag, escapeExtraTags) + ' */\n'; | ||
const emitted = new Set<string>(); | ||
for (let tag of tags) { | ||
for (const tag of tags) { | ||
if (emitted.has(tag.tagName) && SINGLETON_TAGS.has(tag.tagName)) { | ||
@@ -282,6 +285,6 @@ continue; | ||
export function merge(tags: Tag[]): Tag { | ||
let tagNames = new Set<string>(); | ||
let parameterNames = new Set<string>(); | ||
let types = new Set<string>(); | ||
let texts = new Set<string>(); | ||
const tagNames = new Set<string>(); | ||
const parameterNames = new Set<string>(); | ||
const types = new Set<string>(); | ||
const texts = new Set<string>(); | ||
// If any of the tags are optional/rest, then the merged output is optional/rest. | ||
@@ -307,3 +310,3 @@ let optional = false; | ||
const text = texts.size > 0 ? Array.from(texts).join(' / ') : undefined; | ||
let tag: Tag = {tagName, parameterName, type, text}; | ||
const tag: Tag = {tagName, parameterName, type, text}; | ||
if (optional) tag.optional = true; | ||
@@ -310,0 +313,0 @@ if (restParam) tag.restParam = true; |
@@ -49,5 +49,5 @@ #!/usr/bin/env node | ||
function loadSettingsFromArgs(args: string[]): {settings: Settings, tscArgs: string[]} { | ||
let settings: Settings = {}; | ||
let parsedArgs = minimist(args); | ||
for (let flag of Object.keys(parsedArgs)) { | ||
const settings: Settings = {}; | ||
const parsedArgs = minimist(args); | ||
for (const flag of Object.keys(parsedArgs)) { | ||
switch (flag) { | ||
@@ -78,3 +78,3 @@ case 'h': | ||
// Arguments after the '--' arg are arguments to tsc. | ||
let tscArgs = parsedArgs['_']; | ||
const tscArgs = parsedArgs['_']; | ||
return {settings, tscArgs}; | ||
@@ -97,2 +97,3 @@ } | ||
// https://github.com/Microsoft/TypeScript/issues/2620 | ||
// tslint:disable-next-line:no-any | ||
let {options, fileNames, errors} = (ts as any).parseCommandLine(args); | ||
@@ -105,8 +106,8 @@ if (errors.length > 0) { | ||
// Store file arguments | ||
let tsFileArguments = fileNames; | ||
const tsFileArguments = fileNames; | ||
// Read further settings from tsconfig.json. | ||
let projectDir = options.project || '.'; | ||
let configFileName = path.join(projectDir, 'tsconfig.json'); | ||
let {config: json, error} = | ||
const projectDir = options.project || '.'; | ||
const configFileName = path.join(projectDir, 'tsconfig.json'); | ||
const {config: json, error} = | ||
ts.readConfigFile(configFileName, path => fs.readFileSync(path, 'utf-8')); | ||
@@ -184,3 +185,3 @@ if (error) { | ||
{ // Scope for the "diagnostics" variable so we can use the name again later. | ||
let diagnostics = ts.getPreEmitDiagnostics(program); | ||
const diagnostics = ts.getPreEmitDiagnostics(program); | ||
if (diagnostics.length > 0) { | ||
@@ -204,3 +205,3 @@ allDiagnostics.push(...diagnostics); | ||
let {diagnostics} = program.emit(undefined); | ||
const {diagnostics} = program.emit(undefined); | ||
if (diagnostics.length > 0) { | ||
@@ -215,5 +216,5 @@ allDiagnostics.push(...diagnostics); | ||
function main(args: string[]): number { | ||
let {settings, tscArgs} = loadSettingsFromArgs(args); | ||
let diagnostics: ts.Diagnostic[] = []; | ||
let config = loadTscConfig(tscArgs, diagnostics); | ||
const {settings, tscArgs} = loadSettingsFromArgs(args); | ||
const diagnostics: ts.Diagnostic[] = []; | ||
const config = loadTscConfig(tscArgs, diagnostics); | ||
if (config === null) { | ||
@@ -233,3 +234,3 @@ console.error(tsickle.formatDiagnostics(diagnostics)); | ||
// Run tsickle+TSC to convert inputs to Closure JS files. | ||
let closure = toClosureJS(config.options, config.fileNames, settings, diagnostics); | ||
const closure = toClosureJS(config.options, config.fileNames, settings, diagnostics); | ||
if (closure === null) { | ||
@@ -240,3 +241,3 @@ console.error(tsickle.formatDiagnostics(diagnostics)); | ||
for (let fileName of toArray(closure.jsFiles.keys())) { | ||
for (const fileName of toArray(closure.jsFiles.keys())) { | ||
mkdirp.sync(path.dirname(fileName)); | ||
@@ -243,0 +244,0 @@ fs.writeFileSync(fileName, closure.jsFiles.get(fileName)); |
@@ -18,2 +18,7 @@ /** | ||
addManifest(other: ModulesManifest) { | ||
Object.assign(this.moduleToFileName, other.moduleToFileName); | ||
Object.assign(this.referencedModules, other.referencedModules); | ||
} | ||
addModule(fileName: string, module: string): void { | ||
@@ -20,0 +25,0 @@ this.moduleToFileName[module] = fileName; |
@@ -9,5 +9,6 @@ /** | ||
import {SourceMapGenerator} from 'source-map'; | ||
import * as ts from 'typescript'; | ||
import {NOOP_SOURCE_MAPPER, SourceMapper, SourcePosition} from './source_map_utils'; | ||
/** | ||
@@ -22,6 +23,4 @@ * A Rewriter manages iterating through a ts.SourceFile, copying input | ||
protected diagnostics: ts.Diagnostic[] = []; | ||
/** The source map that's generated while rewriting this file. */ | ||
private sourceMap: SourceMapGenerator; | ||
/** Current position in the output. */ | ||
private position = {line: 1, column: 1}; | ||
private position: SourcePosition = {line: 0, column: 0, position: 0}; | ||
/** | ||
@@ -31,14 +30,13 @@ * The current level of recursion through TypeScript Nodes. Used in formatting internal debug | ||
*/ | ||
private indent: number = 0; | ||
private indent = 0; | ||
/** | ||
* Skip emitting any code before the given offset. E.g. used to avoid emitting @fileoverview | ||
* comments twice. | ||
*/ | ||
private skipUpToOffset = -1; | ||
constructor(protected file: ts.SourceFile) { | ||
this.sourceMap = new SourceMapGenerator({file: file.fileName}); | ||
this.sourceMap.addMapping({ | ||
original: this.position, | ||
generated: this.position, | ||
source: file.fileName, | ||
}); | ||
constructor(public file: ts.SourceFile, private sourceMapper: SourceMapper = NOOP_SOURCE_MAPPER) { | ||
} | ||
getOutput(): {output: string, diagnostics: ts.Diagnostic[], sourceMap: SourceMapGenerator} { | ||
getOutput(): {output: string, diagnostics: ts.Diagnostic[]} { | ||
if (this.indent !== 0) { | ||
@@ -50,3 +48,2 @@ throw new Error('visit() failed to track nesting'); | ||
diagnostics: this.diagnostics, | ||
sourceMap: this.sourceMap, | ||
}; | ||
@@ -78,3 +75,3 @@ } | ||
/** writeNode writes a ts.Node, calling this.visit() on its children. */ | ||
writeNode(node: ts.Node, skipComments = false) { | ||
writeNode(node: ts.Node, skipComments = false, newLineIfCommentsStripped = true) { | ||
let pos = node.getFullStart(); | ||
@@ -87,3 +84,3 @@ if (skipComments) { | ||
// there wasn't any comment. | ||
if (node.getFullStart() < node.getStart()) { | ||
if (newLineIfCommentsStripped && node.getFullStart() < node.getStart()) { | ||
this.emit('\n'); | ||
@@ -93,17 +90,21 @@ } | ||
} | ||
this.writeNodeFrom(node, pos); | ||
} | ||
writeNodeFrom(node: ts.Node, pos: number, end = node.getEnd()) { | ||
if (end <= this.skipUpToOffset) { | ||
return; | ||
} | ||
const oldSkipUpToOffset = this.skipUpToOffset; | ||
this.skipUpToOffset = Math.max(this.skipUpToOffset, pos); | ||
ts.forEachChild(node, child => { | ||
this.writeRange(pos, child.getFullStart()); | ||
this.writeRange(node, pos, child.getFullStart()); | ||
this.visit(child); | ||
pos = child.getEnd(); | ||
}); | ||
this.writeRange(pos, node.getEnd()); | ||
this.writeRange(node, pos, end); | ||
this.skipUpToOffset = oldSkipUpToOffset; | ||
} | ||
/** | ||
* Skip emitting any code before the given offset. Used to avoid emitting @fileoverview comments | ||
* twice. | ||
*/ | ||
protected skipUpToOffset = 0; | ||
/** | ||
* Write a span of the input file as expressed by absolute offsets. | ||
@@ -113,4 +114,16 @@ * These offsets are found in attributes like node.getFullStart() and | ||
*/ | ||
writeRange(from: number, to: number) { | ||
writeRange(node: ts.Node, from: number, to: number) { | ||
from = Math.max(from, this.skipUpToOffset); | ||
if (from !== to && to <= this.skipUpToOffset) { | ||
return; | ||
} | ||
// Add a source mapping. writeRange(from, to) always corresponds to | ||
// original source code, so add a mapping at the current location that | ||
// points back to the location at `from`. The additional code generated | ||
// by tsickle will then be considered part of the last mapped code | ||
// section preceding it. That's arguably incorrect (e.g. for the fake | ||
// methods defining properties), but is good enough for stack traces. | ||
const pos = this.file.getLineAndCharacterOfPosition(from); | ||
this.sourceMapper.addMapping( | ||
node, {line: pos.line, column: pos.character, position: from}, this.position, to - from); | ||
// getSourceFile().getText() is wrong here because it has the text of | ||
@@ -120,16 +133,4 @@ // the SourceFile node of the AST, which doesn't contain the comments | ||
// into the original source file text, so slice from that. | ||
let text = this.file.text.slice(from, to); | ||
const text = this.file.text.slice(from, to); | ||
if (text) { | ||
// Add a source mapping. writeRange(from, to) always corresponds to | ||
// original source code, so add a mapping at the current location that | ||
// points back to the location at `from`. The additional code generated | ||
// by tsickle will then be considered part of the last mapped code | ||
// section preceding it. That's arguably incorrect (e.g. for the fake | ||
// methods defining properties), but is good enough for stack traces. | ||
const pos = this.file.getLineAndCharacterOfPosition(from); | ||
this.sourceMap.addMapping({ | ||
original: {line: pos.line + 1, column: pos.character + 1}, | ||
generated: this.position, | ||
source: this.file.fileName, | ||
}); | ||
this.emit(text); | ||
@@ -145,5 +146,6 @@ } | ||
this.position.line++; | ||
this.position.column = 1; | ||
this.position.column = 0; | ||
} | ||
} | ||
this.position.position += str.length; | ||
} | ||
@@ -159,3 +161,3 @@ | ||
/* tslint:enable: no-unused-variable */ | ||
let prefix = new Array(this.indent + 1).join('| '); | ||
const prefix = new Array(this.indent + 1).join('| '); | ||
console.log(prefix + message); | ||
@@ -178,3 +180,3 @@ } | ||
length: node.getEnd() - node.getStart(), | ||
messageText: messageText, | ||
messageText, | ||
category: ts.DiagnosticCategory.Error, | ||
@@ -181,0 +183,0 @@ code: 0, |
@@ -9,3 +9,4 @@ /** | ||
import {SourceMapConsumer, SourceMapGenerator} from 'source-map'; | ||
import {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map'; | ||
import * as ts from 'typescript'; | ||
@@ -65,2 +66,13 @@ /** | ||
export function parseSourceMap(text: string, fileName?: string, sourceName?: string): RawSourceMap { | ||
const rawSourceMap = JSON.parse(text) as RawSourceMap; | ||
if (sourceName) { | ||
rawSourceMap.sources = [sourceName]; | ||
} | ||
if (fileName) { | ||
rawSourceMap.file = fileName; | ||
} | ||
return rawSourceMap; | ||
} | ||
export function sourceMapConsumerToGenerator(sourceMapConsumer: SourceMapConsumer): | ||
@@ -91,2 +103,3 @@ SourceMapGenerator { | ||
export function sourceMapTextToConsumer(sourceMapText: string): SourceMapConsumer { | ||
// tslint:disable-next-line:no-any constructor actually supports text. | ||
const sourceMapJson: any = sourceMapText; | ||
@@ -97,4 +110,49 @@ return new SourceMapConsumer(sourceMapJson); | ||
export function sourceMapTextToGenerator(sourceMapText: string): SourceMapGenerator { | ||
// tslint:disable-next-line:no-any constructor actually supports text. | ||
const sourceMapJson: any = sourceMapText; | ||
return SourceMapGenerator.fromSourceMap(sourceMapTextToConsumer(sourceMapJson)); | ||
} | ||
export interface SourcePosition { | ||
// 0 based | ||
column: number; | ||
// 0 based | ||
line: number; | ||
// 0 based | ||
position: number; | ||
} | ||
export interface SourceMapper { | ||
addMapping( | ||
originalNode: ts.Node, original: SourcePosition, generated: SourcePosition, | ||
length: number): void; | ||
} | ||
export const NOOP_SOURCE_MAPPER: SourceMapper = { | ||
// tslint:disable-next-line:no-empty | ||
addMapping() {} | ||
}; | ||
export class DefaultSourceMapper implements SourceMapper { | ||
/** The source map that's generated while rewriting this file. */ | ||
public sourceMap = new SourceMapGenerator(); | ||
constructor(private fileName: string) { | ||
this.sourceMap.addMapping({ | ||
original: {line: 1, column: 1}, | ||
generated: {line: 1, column: 1}, | ||
source: this.fileName, | ||
}); | ||
} | ||
addMapping(node: ts.Node, original: SourcePosition, generated: SourcePosition, length: number): | ||
void { | ||
if (length > 0) { | ||
this.sourceMap.addMapping({ | ||
original: {line: original.line + 1, column: original.column + 1}, | ||
generated: {line: generated.line + 1, column: generated.column + 1}, | ||
source: this.fileName, | ||
}); | ||
} | ||
} | ||
} |
@@ -14,3 +14,3 @@ /** | ||
import {convertDecorators} from './decorator-annotator'; | ||
import {processES5} from './es5processor'; | ||
import {convertCommonJsToGoogModuleIfNeeded, Es5ProcessorHost, Es5ProcessorOptions} from './es5processor'; | ||
import {ModulesManifest} from './modules_manifest'; | ||
@@ -30,30 +30,21 @@ import * as sourceMapUtils from './source_map_utils'; | ||
NONE, | ||
DECORATOR_DOWNLEVEL, | ||
CLOSURIZE | ||
} | ||
export interface Options { | ||
googmodule?: boolean; | ||
es5Mode?: boolean; | ||
prelude?: string; | ||
/** | ||
* If true, convert every type to the Closure {?} type, which means | ||
* "don't check types". | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
untyped?: boolean; | ||
DECORATOR_DOWNLEVEL, | ||
/** | ||
* If provided a function that logs an internal warning. | ||
* These warnings are not actionable by an end user and should be hidden | ||
* by default. | ||
* Note that we can do decorator downlevel and closurize in one pass, | ||
* so this should not be used anymore. | ||
*/ | ||
logWarning?: (warning: ts.Diagnostic) => void; | ||
/** If provided, a set of paths whose types should always generate as {?}. */ | ||
typeBlackListPaths?: Set<string>; | ||
/** | ||
* Convert shorthand "/index" imports to full path (include the "/index"). | ||
* Annotation will be slower because every import must be resolved. | ||
*/ | ||
convertIndexImportShorthand?: boolean; | ||
CLOSURIZE, | ||
DECORATOR_DOWNLEVEL_AND_CLOSURIZE, | ||
} | ||
export interface Options extends Es5ProcessorOptions, tsickle.AnnotatorOptions { | ||
// This method is here for backwards compatibility. | ||
// Use the method in TsickleHost instead. | ||
logWarning?: TsickleHost['logWarning']; | ||
} | ||
/** | ||
@@ -63,3 +54,3 @@ * Provides hooks to customize TsickleCompilerHost's behavior for different | ||
*/ | ||
export interface TsickleHost { | ||
export interface TsickleHost extends Es5ProcessorHost, tsickle.AnnotatorHost { | ||
/** | ||
@@ -71,7 +62,2 @@ * If true, tsickle and decorator downlevel processing will be skipped for | ||
/** | ||
* Takes a context (the current file) and the path of the file to import | ||
* and generates a googmodule module name | ||
*/ | ||
pathToModuleName(context: string, importPath: string): string; | ||
/** | ||
* Tsickle treats warnings as errors, if true, ignore warnings. This might be | ||
@@ -81,8 +67,2 @@ * useful for e.g. third party code. | ||
shouldIgnoreWarningsForPath(filePath: string): boolean; | ||
/** | ||
* If we do googmodule processing, we polyfill module.id, since that's | ||
* part of ES6 modules. This function determines what the module.id will be | ||
* for each file. | ||
*/ | ||
fileNameToModuleId(fileName: string): string; | ||
} | ||
@@ -97,3 +77,3 @@ | ||
// The manifest of JS modules output by the compiler. | ||
public modulesManifest: ModulesManifest = new ModulesManifest(); | ||
public modulesManifest = new ModulesManifest(); | ||
@@ -116,2 +96,5 @@ /** Error messages produced by tsickle, if any. */ | ||
private options: Options, private environment: TsickleHost) { | ||
if (options.logWarning && !environment.logWarning) { | ||
environment.logWarning = options.logWarning; | ||
} | ||
// ts.CompilerHost includes a bunch of optional methods. If they're | ||
@@ -157,2 +140,3 @@ // present on the delegate host, we want to delegate them. | ||
this.runConfiguration = {oldProgram, pass}; | ||
this.diagnostics = []; | ||
} | ||
@@ -175,3 +159,8 @@ | ||
return this.closurize( | ||
sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion); | ||
sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion, | ||
/* downlevelDecorators */ false); | ||
case Pass.DECORATOR_DOWNLEVEL_AND_CLOSURIZE: | ||
return this.closurize( | ||
sourceFile, this.runConfiguration.oldProgram, fileName, languageVersion, | ||
/* downlevelDecorators */ true); | ||
default: | ||
@@ -189,5 +178,3 @@ throw new Error('tried to use TsickleCompilerHost with unknown pass enum'); | ||
} | ||
if (this.options.googmodule && !isDtsFileName(fileName)) { | ||
content = this.convertCommonJsToGoogModule(fileName, content); | ||
} | ||
content = this.convertCommonJsToGoogModule(fileName, content); | ||
} else { | ||
@@ -230,2 +217,4 @@ content = this.combineSourceMaps(fileName, content); | ||
combineSourceMaps(filePath: string, tscSourceMapText: string): string { | ||
const printDebugInfo = false; | ||
// const printDebugInfo = true; | ||
// We stripe inline source maps off source files before they've been parsed | ||
@@ -245,4 +234,10 @@ // which is before they have path properties, so we need to construct the | ||
if (printDebugInfo) { | ||
console.error(`tsc source map for ${filePath}`); | ||
console.error(tscSourceMapGenerator.toString()); | ||
} | ||
if (this.tsickleSourceMaps.size > 0) { | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (const sourceFileName of (tscSourceMapConsumer as any).sources) { | ||
@@ -254,2 +249,6 @@ const sourceMapKey = this.getSourceMapKeyForPathAndName(filePath, sourceFileName); | ||
tscSourceMapGenerator.applySourceMap(tsickleSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error(`tsickle source map for ${filePath}`); | ||
console.error(tsickleSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -259,2 +258,3 @@ } | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (const sourceFileName of (tscSourceMapConsumer as any).sources) { | ||
@@ -267,2 +267,6 @@ const sourceMapKey = this.getSourceMapKeyForPathAndName(filePath, sourceFileName); | ||
tscSourceMapGenerator.applySourceMap(decoratorDownlevelSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error(`decorator downlevel sourcemap for ${filePath}`); | ||
console.error(decoratorDownlevelSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -272,2 +276,3 @@ } | ||
// TODO(lucassloan): remove when the .d.ts has the correct types | ||
// tslint:disable-next-line:no-any | ||
for (const sourceFileName of (tscSourceMapConsumer as any).sources) { | ||
@@ -280,2 +285,6 @@ const sourceMapKey = this.getSourceMapKeyForPathAndName(filePath, sourceFileName); | ||
tscSourceMapGenerator.applySourceMap(preexistingSourceMapConsumer); | ||
if (printDebugInfo) { | ||
console.error(`preexisting source map for ${filePath}`); | ||
console.error(preexistingSourceMapGenerator.toString()); | ||
} | ||
} | ||
@@ -296,15 +305,4 @@ } | ||
convertCommonJsToGoogModule(fileName: string, content: string): string { | ||
const moduleId = this.environment.fileNameToModuleId(fileName); | ||
let {output, referencedModules} = processES5( | ||
fileName, moduleId, content, this.environment.pathToModuleName.bind(this.environment), | ||
this.options.es5Mode, this.options.prelude); | ||
const moduleName = this.environment.pathToModuleName('', fileName); | ||
this.modulesManifest.addModule(fileName, moduleName); | ||
for (let referenced of referencedModules) { | ||
this.modulesManifest.addReferencedModule(fileName, referenced); | ||
} | ||
return output; | ||
return convertCommonJsToGoogModuleIfNeeded( | ||
this.environment, this.options, this.modulesManifest, fileName, content); | ||
} | ||
@@ -319,3 +317,4 @@ | ||
let fileContent = sourceFile.text; | ||
const converted = convertDecorators(program.getTypeChecker(), sourceFile); | ||
const sourceMapper = new sourceMapUtils.DefaultSourceMapper(sourceFile.fileName); | ||
const converted = convertDecorators(program.getTypeChecker(), sourceFile, sourceMapper); | ||
if (converted.diagnostics) { | ||
@@ -330,3 +329,3 @@ this.diagnostics.push(...converted.diagnostics); | ||
this.decoratorDownlevelSourceMaps.set( | ||
this.getSourceMapKeyForSourceFile(sourceFile), converted.sourceMap); | ||
this.getSourceMapKeyForSourceFile(sourceFile), sourceMapper.sourceMap); | ||
return ts.createSourceFile(fileName, fileContent, languageVersion, true); | ||
@@ -337,6 +336,6 @@ } | ||
sourceFile: ts.SourceFile, program: ts.Program, fileName: string, | ||
languageVersion: ts.ScriptTarget): ts.SourceFile { | ||
languageVersion: ts.ScriptTarget, downlevelDecorators: boolean): ts.SourceFile { | ||
this.tsickleSourceMaps.set( | ||
this.getSourceMapKeyForSourceFile(sourceFile), new SourceMapGenerator()); | ||
let isDefinitions = isDtsFileName(fileName); | ||
const isDefinitions = isDtsFileName(fileName); | ||
// Don't tsickle-process any d.ts that isn't a compilation target; | ||
@@ -346,7 +345,13 @@ // this means we don't process e.g. lib.d.ts. | ||
let {output, externs, diagnostics, sourceMap} = tsickle.annotate( | ||
program, sourceFile, this.environment.pathToModuleName.bind(this.environment), this.options, | ||
this.delegate, this.tscOptions); | ||
const sourceMapper = new sourceMapUtils.DefaultSourceMapper(sourceFile.fileName); | ||
const annotated = tsickle.annotate( | ||
program.getTypeChecker(), sourceFile, this.environment, this.options, this.delegate, | ||
this.tscOptions, sourceMapper, | ||
downlevelDecorators ? tsickle.AnnotatorFeatures.LowerDecorators : | ||
tsickle.AnnotatorFeatures.Default); | ||
const externs = | ||
tsickle.writeExterns(program.getTypeChecker(), sourceFile, this.environment, this.options); | ||
let diagnostics = externs.diagnostics.concat(annotated.diagnostics); | ||
if (externs) { | ||
this.externs[fileName] = externs; | ||
this.externs[fileName] = externs.output; | ||
} | ||
@@ -360,5 +365,6 @@ if (this.environment.shouldIgnoreWarningsForPath(sourceFile.fileName)) { | ||
} | ||
this.diagnostics = diagnostics; | ||
this.tsickleSourceMaps.set(this.getSourceMapKeyForSourceFile(sourceFile), sourceMap); | ||
return ts.createSourceFile(fileName, output, languageVersion, true); | ||
this.diagnostics.push(...diagnostics); | ||
this.tsickleSourceMaps.set( | ||
this.getSourceMapKeyForSourceFile(sourceFile), sourceMapper.sourceMap); | ||
return ts.createSourceFile(fileName, annotated.output, languageVersion, true); | ||
} | ||
@@ -368,8 +374,3 @@ | ||
getGeneratedExterns(): string { | ||
let allExterns = tsickle.EXTERNS_HEADER; | ||
for (let fileName of Object.keys(this.externs)) { | ||
allExterns += `// externs from ${fileName}:\n`; | ||
allExterns += this.externs[fileName]; | ||
} | ||
return allExterns; | ||
return tsickle.getGeneratedExterns(this.externs); | ||
} | ||
@@ -384,3 +385,3 @@ | ||
return this.delegate.getCurrentDirectory(); | ||
}; | ||
} | ||
@@ -387,0 +388,0 @@ useCaseSensitiveFileNames(): boolean { |
@@ -51,3 +51,3 @@ /** | ||
]; | ||
for (let flag of basicTypes) { | ||
for (const flag of basicTypes) { | ||
if ((type.flags & flag) !== 0) { | ||
@@ -72,3 +72,3 @@ debugString += ` ${ts.TypeFlags[flag]}`; | ||
]; | ||
for (let flag of objectFlags) { | ||
for (const flag of objectFlags) { | ||
if ((objType.objectFlags & flag) !== 0) { | ||
@@ -154,4 +154,3 @@ debugString += ` object:${ts.ObjectFlags[flag]}`; | ||
private readonly pathBlackList?: Set<string>, | ||
private readonly symbolsToAliasedNames: | ||
Map<ts.Symbol, string> = new Map<ts.Symbol, string>()) { | ||
private readonly symbolsToAliasedNames = new Map<ts.Symbol, string>()) { | ||
// Normalize paths to not break checks on Windows. | ||
@@ -175,3 +174,7 @@ if (this.pathBlackList != null) { | ||
let str = ''; | ||
let alias = this.symbolsToAliasedNames.get(sym); | ||
let symAlias = sym; | ||
if (symAlias.flags & ts.SymbolFlags.Alias) { | ||
symAlias = this.typeChecker.getAliasedSymbol(symAlias); | ||
} | ||
const alias = this.symbolsToAliasedNames.get(symAlias); | ||
if (alias) return alias; | ||
@@ -189,9 +192,9 @@ if (useFqn) { | ||
let writeText = (text: string) => str += text; | ||
let doNothing = () => { | ||
const writeText = (text: string) => str += text; | ||
const doNothing = () => { | ||
return; | ||
}; | ||
let builder = this.typeChecker.getSymbolDisplayBuilder(); | ||
let writer: ts.SymbolWriter = { | ||
const builder = this.typeChecker.getSymbolDisplayBuilder(); | ||
const writer: ts.SymbolWriter = { | ||
writeKeyword: writeText, | ||
@@ -237,4 +240,4 @@ writeOperator: writeText, | ||
// in this switch statement, and should be kept in sync with typescript.d.ts. | ||
let lastFlag = ts.TypeFlags.IndexedAccess; | ||
let mask = (lastFlag << 1) - 1; | ||
const lastFlag = ts.TypeFlags.IndexedAccess; | ||
const mask = (lastFlag << 1) - 1; | ||
switch (type.flags & mask) { | ||
@@ -355,3 +358,3 @@ case ts.TypeFlags.Any: | ||
// Emit the referenced type and any type arguments. | ||
let referenceType = type as ts.TypeReference; | ||
const referenceType = type as ts.TypeReference; | ||
@@ -376,3 +379,3 @@ // A tuple is a ReferenceType where the target is flagged Tuple and the | ||
if (referenceType.typeArguments) { | ||
let params = referenceType.typeArguments.map(t => this.translate(t)); | ||
const params = referenceType.typeArguments.map(t => this.translate(t)); | ||
typeStr += `<${params.join(', ')}>`; | ||
@@ -391,8 +394,8 @@ } | ||
if (type.symbol.flags === ts.SymbolFlags.TypeLiteral) { | ||
if (type.symbol.flags & ts.SymbolFlags.TypeLiteral) { | ||
return this.translateTypeLiteral(type); | ||
} else if ( | ||
type.symbol.flags === ts.SymbolFlags.Function || | ||
type.symbol.flags === ts.SymbolFlags.Method) { | ||
let sigs = this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); | ||
type.symbol.flags & ts.SymbolFlags.Function || | ||
type.symbol.flags & ts.SymbolFlags.Method) { | ||
const sigs = this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); | ||
if (sigs.length === 1) { | ||
@@ -435,3 +438,3 @@ return this.signatureToClosure(sigs[0]); | ||
let indexable = false; | ||
let fields: string[] = []; | ||
const fields: string[] = []; | ||
if (!type.symbol || !type.symbol.members) { | ||
@@ -461,3 +464,3 @@ this.warn('type literal has no symbol'); | ||
for (let field of toArray(type.symbol.members.keys())) { | ||
for (const field of toArray(type.symbol.members.keys())) { | ||
switch (field) { | ||
@@ -471,7 +474,8 @@ case '__call': | ||
default: | ||
let member = type.symbol.members.get(field)!; | ||
const member = type.symbol.members.get(field)!; | ||
// optional members are handled by the type including |undefined in a union type. | ||
let memberType = | ||
const memberType = | ||
this.translate(this.typeChecker.getTypeOfSymbolAtLocation(member, this.node)); | ||
fields.push(`${field}: ${memberType}`); | ||
break; | ||
} | ||
@@ -484,3 +488,3 @@ } | ||
// A function type. | ||
let sigs = this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); | ||
const sigs = this.typeChecker.getSignaturesOfType(type, ts.SignatureKind.Call); | ||
if (sigs.length === 1) { | ||
@@ -520,6 +524,6 @@ return this.signatureToClosure(sigs[0]); | ||
private signatureToClosure(sig: ts.Signature): string { | ||
let params = this.convertParams(sig); | ||
const params = this.convertParams(sig); | ||
let typeStr = `function(${params.join(', ')})`; | ||
let retType = this.translate(this.typeChecker.getReturnTypeOfSignature(sig)); | ||
const retType = this.translate(this.typeChecker.getReturnTypeOfSignature(sig)); | ||
if (retType) { | ||
@@ -534,3 +538,3 @@ typeStr += `: ${retType}`; | ||
return sig.parameters.map(param => { | ||
let paramType = this.typeChecker.getTypeOfSymbolAtLocation(param, this.node); | ||
const paramType = this.typeChecker.getTypeOfSymbolAtLocation(param, this.node); | ||
return this.translate(paramType); | ||
@@ -549,6 +553,4 @@ }); | ||
const pathBlackList = this.pathBlackList; | ||
if (symbol.declarations === undefined) { | ||
this.warn('symbol has no declarations'); | ||
return true; | ||
} | ||
// Some builtin types, such as {}, get represented by a symbol that has no declarations. | ||
if (symbol.declarations === undefined) return false; | ||
return symbol.declarations.every(n => { | ||
@@ -555,0 +557,0 @@ const fileName = path.normalize(n.getSourceFile().fileName); |
@@ -53,4 +53,4 @@ /** | ||
onError?: (message: string) => void): ts.SourceFile { | ||
let path: string = ts.sys.resolvePath(fileName); | ||
let sourceText = substituteSource.get(path); | ||
const path: string = ts.sys.resolvePath(fileName); | ||
const sourceText = substituteSource.get(path); | ||
if (sourceText !== undefined) { | ||
@@ -75,3 +75,3 @@ return ts.createSourceFile(fileName, sourceText, languageVersion); | ||
getDefaultLibFileName: delegate.getDefaultLibFileName, | ||
writeFile: writeFile, | ||
writeFile, | ||
getCurrentDirectory: delegate.getCurrentDirectory, | ||
@@ -93,1 +93,8 @@ getCanonicalFileName: delegate.getCanonicalFileName, | ||
} | ||
/** | ||
* Returns the input string with line endings normalized to '\n'. | ||
*/ | ||
export function normalizeLineEndings(input: string): string { | ||
return input.replace(/\r\n/g, '\n'); | ||
} |
{ | ||
"rules": { | ||
"array-type": [true, "array-simple"], | ||
"arrow-return-shorthand": true, | ||
"ban": [true, | ||
["fit"], | ||
["fdescribe"], | ||
["xit"], | ||
["xdescribe"], | ||
["fitAsync"], | ||
["xitAsync"], | ||
["fitFakeAsync"], | ||
["xitFakeAsync"] | ||
], | ||
"ban-types": [true, | ||
["Object", "Use {} instead."], | ||
["String", "Use 'string' instead."], | ||
["Number", "Use 'number' instead."], | ||
["Boolean", "Use 'boolean' instead."] | ||
], | ||
"class-name": true, | ||
"comment-format": [true, "check-space"], | ||
"eofline": true, | ||
"forin": true, | ||
"indent": [true, "spaces"], | ||
"interface-name": [true, "never-prefix"], | ||
"jsdoc-format": true, | ||
"label-position": true, | ||
"label-undefined": true, | ||
"no-arg": true, | ||
"no-conditional-assignment": true, | ||
"new-parens": true, | ||
"no-angle-bracket-type-assertion": true, | ||
"no-any": true, | ||
"no-construct": true, | ||
"no-debugger": true, | ||
"no-duplicate-key": true, | ||
"no-duplicate-variable": true, | ||
"no-empty": true, | ||
"no-default-export": true, | ||
"no-inferrable-types": true, | ||
"no-internal-module": true, | ||
"no-shadowed-variable": true, | ||
"no-switch-case-fall-through": true, | ||
"no-trailing-whitespace": true, | ||
"no-unreachable": true, | ||
"no-namespace": [true, "allow-declarations"], | ||
"no-reference": true, | ||
"no-require-imports": true, | ||
"no-unused-expression": true, | ||
"no-unused-variable": true, | ||
"no-use-before-declare": true, | ||
"no-use-before-declare": false, | ||
"no-var-keyword": true, | ||
"quotemark": [true, "single"], | ||
"object-literal-shorthand": true, | ||
"only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], | ||
"prefer-const": true, | ||
"radix": true, | ||
"semicolon": [true, "always"], | ||
"semicolon": [true, "always", "ignore-bound-class-methods"], | ||
"no-string-throw": true, | ||
"switch-default": true, | ||
"triple-equals": [true, "allow-null-check"], | ||
"variable-name": [true, "check-format", "ban-keywords"] | ||
"use-isnan": true, | ||
"variable-name": [ | ||
true, | ||
"check-format", | ||
"ban-keywords", | ||
"allow-leading-underscore", | ||
"allow-trailing-underscore" | ||
] | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
782349
82
11716
28