Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Introduce transformer support for tsickle #516

Merged
merged 8 commits into from
Jun 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"devDependencies": {
"@types/chai": "^3.4.32",
"@types/diff": "^3.2.0",
"@types/glob": "^5.0.29",
"@types/google-closure-compiler": "0.0.18",
"@types/minimatch": "^2.0.28",
Expand All @@ -30,6 +31,7 @@
"@types/source-map-support": "^0.2.27",
"chai": "^3.5.0",
"clang-format": "^1.0.51",
"diff": "^3.2.0",
"glob": "^7.0.0",
"google-closure-compiler": "^20161024.1.0",
"gulp": "^3.8.11",
Expand Down
111 changes: 64 additions & 47 deletions src/decorator-annotator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ export class DecoratorClassVisitor {
/** Decorators on the class itself. */
decorators: ts.Decorator[];
/** The constructor parameter list and decorators on each param. */
ctorParameters: Array<[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, private rewriter: Rewriter,
private classDecl: ts.ClassDeclaration) {
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;
Expand Down Expand Up @@ -80,10 +81,11 @@ export class DecoratorClassVisitor {
* constructor, and emits nothing.
*/
private gatherConstructor(ctor: ts.ConstructorDeclaration) {
const ctorParameters: Array<[string | undefined, ts.Decorator[] | undefined]|null> = [];
const ctorParameters:
Array<[[ts.Symbol, string] | undefined, ts.Decorator[] | undefined]|null> = [];
let hasDecoratedParam = false;
for (const param of ctor.parameters) {
let paramCtor: string|undefined;
let paramCtor: [ts.Symbol, string]|undefined;
let decorators: ts.Decorator[]|undefined;
if (param.decorators) {
decorators = this.decoratorsToLower(param);
Expand All @@ -94,8 +96,9 @@ export class DecoratorClassVisitor {
// Verify that "Bar" is a value (e.g. a constructor) and not just a type.
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];
}
}
if (paramCtor || decorators) {
Expand Down Expand Up @@ -130,6 +133,38 @@ export class DecoratorClassVisitor {
this.propDecorators.set(name, decorators);
}

/**
* 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: 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.Constructor:
Expand Down Expand Up @@ -205,22 +240,28 @@ export class DecoratorClassVisitor {
this.rewriter.emit(
`static ctorParameters: () => ({type: any, decorators?: ` + decoratorInvocations +
`}|null)[] = () => [\n`);
let emittedInline = false;
for (const param of this.ctorParameters || []) {
if (!param) {
if (emittedInline) {
this.rewriter.emit(' ');
}
this.rewriter.emit('null,');
emittedInline = true;
this.rewriter.emit('null,\n');
continue;
}
if (emittedInline) {
this.rewriter.emit('\n');
emittedInline = false;
}
const [ctor, decorators] = param;
this.rewriter.emit(`{type: ${ctor}, `);
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.rewriter.emit('decorators: [');
for (const decorator of decorators) {
Expand Down Expand Up @@ -265,7 +306,7 @@ export class DecoratorClassVisitor {
if (call.arguments.length) {
this.rewriter.emit(', args: [');
for (const arg of call.arguments) {
this.rewriter.emit(arg.getText());
this.rewriter.writeNodeFrom(arg, arg.getStart());
this.rewriter.emit(', ');
}
this.rewriter.emit(']');
Expand Down Expand Up @@ -303,7 +344,7 @@ class DecoratorRewriter extends Rewriter {
case ts.SyntaxKind.ClassDeclaration:
const oldDecoratorConverter = this.currentDecoratorConverter;
this.currentDecoratorConverter =
new DecoratorClassVisitor(this.typeChecker, this, node as ts.ClassDeclaration);
new DecoratorClassVisitor(this.typeChecker, this, node as ts.ClassDeclaration, []);
this.writeRange(node, node.getFullStart(), node.getStart());
visitClassContentIncludingDecorators(
node as ts.ClassDeclaration, this, this.currentDecoratorConverter);
Expand All @@ -317,40 +358,16 @@ class DecoratorRewriter extends Rewriter {

export function visitClassContentIncludingDecorators(
classDecl: ts.ClassDeclaration, rewriter: Rewriter, decoratorVisitor?: DecoratorClassVisitor) {
let pos = classDecl.getStart();
if (decoratorVisitor) {
// visit decorators if needed
ts.forEachChild(classDecl, child => {
if (child.kind !== ts.SyntaxKind.Decorator) {
return;
}
// Note: The getFullStart() of the first decorator is the same
// as the getFullStart() of the class declaration.
// Therefore, we need to use Math.max to not print the whitespace
// of the class again.
const childStart = Math.max(pos, child.getFullStart());
rewriter.writeRange(classDecl, pos, childStart);
if (decoratorVisitor.maybeProcessDecorator(child as ts.Decorator, childStart)) {
pos = child.getEnd();
}
});
}
if (classDecl.members.length > 0) {
rewriter.writeRange(classDecl, pos, classDecl.members[0].getFullStart());
for (const member of classDecl.members) {
rewriter.visit(member);
}
pos = classDecl.getLastToken().getFullStart();
if (rewriter.file.text[classDecl.getEnd() - 1] !== '}') {
rewriter.error(classDecl, 'unexpected class terminator');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we "return" here to avoid the rest of the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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 (rewriter.file.text[classDecl.getEnd() - 1] !== '}') {
rewriter.error(classDecl, 'unexpected class terminator');
}
rewriter.writeRange(classDecl, pos, classDecl.getEnd() - 1);
if (decoratorVisitor) {
decoratorVisitor.emitMetadataAsStaticProperties();
}
Expand Down
5 changes: 5 additions & 0 deletions src/modules_manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export class ModulesManifest {
/** Map of file name to arrays of imported googmodule module names */
private referencedModules: FileMap<string[]> = {};

addManifest(other: ModulesManifest) {
Object.assign(this.moduleToFileName, other.moduleToFileName);
Object.assign(this.referencedModules, other.referencedModules);
}

addModule(fileName: string, module: string): void {
this.moduleToFileName[module] = fileName;
this.referencedModules[fileName] = [];
Expand Down
18 changes: 12 additions & 6 deletions src/rewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ export abstract class Rewriter {
*/
private indent = 0;
/**
* 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.
*/
public skipUpToOffset = 0;
private skipUpToOffset = -1;

constructor(public file: ts.SourceFile, private sourceMapper: SourceMapper = NOOP_SOURCE_MAPPER) {
}
Expand Down Expand Up @@ -84,13 +84,19 @@ export abstract class Rewriter {
this.writeNodeFrom(node, pos);
}

writeNodeFrom(node: ts.Node, pos: number) {
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(node, pos, child.getFullStart());
this.visit(child);
pos = child.getEnd();
});
this.writeRange(node, pos, node.getEnd());
this.writeRange(node, pos, end);
this.skipUpToOffset = oldSkipUpToOffset;
}

/**
Expand All @@ -100,7 +106,7 @@ export abstract class Rewriter {
*/
writeRange(node: ts.Node, from: number, to: number) {
from = Math.max(from, this.skipUpToOffset);
if (this.skipUpToOffset > 0 && to <= this.skipUpToOffset) {
if (from !== to && to <= this.skipUpToOffset) {
return;
}
// Add a source mapping. writeRange(from, to) always corresponds to
Expand Down
13 changes: 12 additions & 1 deletion src/source_map_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {SourceMapConsumer, SourceMapGenerator} from 'source-map';
import {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map';
import * as ts from 'typescript';

/**
Expand Down Expand Up @@ -62,6 +62,17 @@ export function setInlineSourceMap(source: string, sourceMap: string): string {
}
}

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):
SourceMapGenerator {
return SourceMapGenerator.fromSourceMap(sourceMapConsumer);
Expand Down
Loading