-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce transformer support for tsickle #516
Conversation
Note: This still has some G3 failures, I am working on them. So it's probably better to wait with the review of my PR until I am really sure G3 is green. |
5988baf
to
80be1c0
Compare
For: - getters - for properties with comments. Refactors and simplifies the emit logic. Fixes bug that prevented closure types for variable declarations as soon as the variable statement had a jsdoc comment.
This is required by closure ES6 to not produce an error. Note: In the current version of tsickle, type comments in interpolations are elided because of angular#411. In the upcoming transformer version, this is fixed though.
This change was introduced for the upcoming transformer version, but this is no longer needed. Introduced by angular@711cfa2. (that commit was missing some files though…)
These test cases were reproductions of cases where the upcoming transformer version of tsickle failed in G3.
- emit more values via `Rewriter.writeRange` so that we can later on map these ranges back to nodes. - New Feature flag `FilterTypesInExportStart`: as the generated code does no longer have TypeScript typechecker information and therefore typescript does not elide exports for types. - New Feature flag `SimpleEnum`: as the changed enum declarations also no longer have TypeScript typechecker information and thefore typescript no longer generates `export.` for all property accesses to the exported symbols. - New Feature flag `TypeDefReexportForInterfaces`: 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.
Allow to: - synthesize and change any comment - work around TypeScript bugs when using synthesized nodes (which are created in any transformer that changes things).
API (see `transformer.ts`): The method `emitWithTsickle` allows to emit any program with tsickle. I.e. this is a replacement for using the `TsickleCompilerHost`. Implementation using existing `DecoratorClassVisitor` and `Annotator`: (see `transformer_sourcemap.ts`): Parses the produced sources back into a `ts.SourceFile` and establishes the links to the original nodes via the information given to the `SourceMapper` interface. Tests via goldens: The transformer version of Tsickle emits slightly different JavaScript (see the previous commit). Therefore, some goldens are slightly different. We use patch files to assert the differences to the regular goldens. Tests for SourceMaps: TypeScript automatically produces source maps for all changes that the transformer does. However, some applications (e.g. Angular) rely on the feature of tsickle to merge source maps that were already present in the input TypeScript code. Therefore, the transformer version still has to implement this merging.
} | ||
pos = classDecl.getLastToken().getFullStart(); | ||
if (rewriter.file.text[classDecl.getEnd() - 1] !== '}') { | ||
rewriter.error(classDecl, 'unexpected class terminator'); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/rewriter.ts
Outdated
@@ -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 = Number.MAX_SAFE_INTEGER) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can write:
writeNodeFrom(node: ts.Node, pos: number, end = node.getEnd()) {
and then drop the Math.min() below as well as calling node.getEnd().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/tsickle.ts
Outdated
@@ -659,7 +660,11 @@ class Annotator extends ClosureRewriter { | |||
// Only emit a type annotation when it's a plain variable and | |||
// not a binding pattern, as Closure doesn't(?) have a syntax | |||
// for annotating binding patterns. See issue #128. | |||
if (varDecl.name.kind === ts.SyntaxKind.Identifier) { | |||
// Don't emit type annotation when the variable statement is a @polymerBehavior, | |||
// as otherweise the polymer closure checker will fail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: otherwise
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/tsickle.ts
Outdated
if (varDecl.name.kind === ts.SyntaxKind.Identifier) { | ||
// Don't emit type annotation when the variable statement is a @polymerBehavior, | ||
// as otherweise the polymer closure checker will fail. | ||
// TODO(tbosch): file an issue for this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix this with a link to the issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now I will keep the comment as filing an issue takes some time...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think I should file an issue against tsickle to create issues for typescript. This way we don't forget to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #529 to track this and linked in the code.
// See comment above. | ||
if (this.templateSpanStackCount > 0) { | ||
this.emit('('); | ||
} | ||
this.emitJSDocType(nnexpr, undefined, type); | ||
// See comment above. | ||
this.emit('(('); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I vaguely suspect that this double-paren is trying to work around the same(?) issue you are. It's pretty weird that we emit three layers of parens here. It's maybe not worth trying to undo in your change, but maybe you could add a TODO?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need the double paren thing for same cases sadly (I saw it via the golden tests) :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the docs there.
src/decorator-annotator.ts
Outdated
@@ -130,6 +133,26 @@ export class DecoratorClassVisitor { | |||
this.propDecorators.set(name, decorators); | |||
} | |||
|
|||
private getValueIdentifierForType(typeSymbol: ts.Symbol): ts.Identifier|null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add docs on this? What is a value identifier (or value declaration)? Something about literal types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For lowering decorators, we need to refer to constructor types. So we start off with the identifiers that represent these types. However, we want to emit them in a value position. TypeScript does not allow to use the same ts.Identifier here. Instead, this method looks for the place where the value that is associated to the type is defined and returns that identifier instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
src/tsickle.ts
Outdated
*/ | ||
FilterTypesInExportStart = 1 << 1, | ||
/** | ||
* emit enums as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment here why you'd want to pass this flag or not? I recall one of these is a temporary thing that we intend to remove. I see below you have the "transformers need: ..." block, but is the intent that we remove all of these flags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not temporary.
TSC works as this:
- parse and type check / create symbol table
- run transformers
- emit
If we are expanding *
into concrete identifiers, TypeScript has no information about them, and therefore cannot elide types from them. So we have to do this.
In the version of tsickle where we create a new pogram, TypeScript will recreate the symbol table and therefore know which types to elide.
So for transformers, we always want to do this.
Without transformers, we must not do this, as we rely on the fact that we produce functions for interfaces, which should not be elided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added all information about the flags directly to the enum members, and documented that these flags will be removed and on by default once the non transformer version is dropped.
src/tsickle.ts
Outdated
return true; | ||
} else { | ||
this.errorUnimplementedKind(decl, 'unexpected kind of import'); | ||
return false; // Use default processing. | ||
} | ||
} | ||
|
||
private collectImportNames(decl: ts.ImportDeclaration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
/** | ||
* Adjusts the given CustomTransformers with additional transformers | ||
* to fix bugs in TypeScript. | ||
* @param given A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XXX finish comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
} | ||
|
||
/** | ||
* And extended version of the TransformationContext that stores the FileContext as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "an"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
/** | ||
* Transform that needs to be executed right before TypeScript's transform. | ||
* | ||
* This prepares the node tree to workaround some bugs in the TypeScript emitter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth filing the bugs upstream? I'm also worried they'll fix the bugs and then break our workarounds, but I'm not sure what we can do in that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #528 to track filing these bugs.
* Transform that needs to be executed after TypeScript's transform. | ||
* | ||
* This fixes places where the TypeScript transformer does not | ||
* emit synthetic comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these bugs? Should we file them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I think I will leave TODOs everywhere for now where I need to file a bug, as that takes a bit (with small reproduction, ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #528 to track filing these bugs.
src/transformer_util.ts
Outdated
} else if ( | ||
parent3 && parent3.kind === ts.SyntaxKind.VariableStatement && | ||
tsickle.hasModifierFlag(parent3, ts.ModifierFlags.Export)) { | ||
// TypeScript ignore synthetic comments on exported variables. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: ignores
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
} | ||
} | ||
} | ||
// TypeScript ignore synthetic comments on reexport / import statements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: ignores
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
|
||
/** | ||
* Synthesizes the comments before and after a node | ||
* befor calling a callback. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't follow what this means, what does it mean to "synthesize a comment"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, comments are just text ranges (i.e. from - to). To synthesize means to create a node that is independent of text ranges and contains the information in itself.
For comments, we go from ts.CommentRange
to ts.SynthesizedComment
. The latter contains a field text
to store the actual comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment.
src/transformer_util.ts
Outdated
return -1; | ||
} | ||
|
||
function visitNodeStatementsWithSynthesizedComments<T extends ts.Node>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
return visitor(node, statements); | ||
} | ||
|
||
function synthesizeDetachedLeadingComments( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs (here and in the rest of the file)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer.ts
Outdated
// All diagnostics (including warnings) are treated as errors. | ||
// If we've decided to ignore them, just discard them. | ||
// Warnings include stuff like "don't use @type in your jsdoc"; tsickle | ||
// warns and then fixes up the code to be Closure-compatible anyway. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rule here is that we error on google3 code, and don't error on third-party code. Is that still preserved here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, via the host.shouldIgnoreWarningsForPath
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the comment.
src/transformer_sourcemap.ts
Outdated
}; | ||
} | ||
|
||
class NodeSourceMapper implements SourceMapper { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs on here and the rest of this file
I think maybe this one could use a @fileOverview, because this is the one that does the clever "map node offsets to nodes" trick
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/tsickle_transformer_test.ts
Outdated
} | ||
const patchPath = calcPatchPath(path); | ||
if (fs.existsSync(patchPath)) { | ||
// Note: the typings for `diff.applyPath` are wrong in that the function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: patch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/tsickle.ts
Outdated
const namedImports = importClause.namedBindings as ts.NamedImports; | ||
names.push(...namedImports.elements.map(e => e.name)); | ||
} | ||
names.forEach(name => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (const name of names) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/tsickle.ts
Outdated
} | ||
const declarationNames: ts.Identifier[] = []; | ||
if (symbol.declarations) { | ||
symbol.declarations.forEach(d => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
/** | ||
* Transform to be used as first transform to reset some state. | ||
*/ | ||
function firstTransform(context: ts.TransformationContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you give this semantic names, e.g. addFileContexts
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
*/ | ||
function firstTransform(context: ts.TransformationContext) { | ||
return (sourceFile: ts.SourceFile) => { | ||
(context as TransformationContext).fileContext = new FileContext(sourceFile); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document that all transformers run in sequence for each file before visiting the next file, which makes this operation (caching the source file here) valid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
/** | ||
* A context that stores information per file to e.g. allow communication | ||
* between transformers. | ||
* Note that the ts.TransformationContext stores context about all files |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe "There is one ts.TransformationContext per emit, but because files are handled sequentially by all transformers, we can store blah here..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
*/ | ||
class FileContext { | ||
syntheticNodeParents = new Map<ts.Node, ts.Node|undefined>(); | ||
importOrReexportDeclarations: Array<ts.Node&{moduleSpecifier: ts.StringLiteral}> = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a ts.ExportDeclaration|ts.ImportDeclaration
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
* | ||
* This prepares the node tree to workaround some bugs in the TypeScript emitter. | ||
*/ | ||
function preTypeScriptTransform(context: ts.TransformationContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semantic name
src/transformer_util.ts
Outdated
const originalNode = ts.getOriginalNode(node); | ||
// Needed so that e.g. `module { ... }` prints the variable statement | ||
// before the closure. | ||
// tslint:disable-next-line:no-any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add a comment why you need the any?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
* This fixes places where the TypeScript transformer does not | ||
* emit synthetic comments. | ||
*/ | ||
function postTypescriptTransform(context: ts.TransformationContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semantic name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_util.ts
Outdated
} | ||
|
||
function synthesizeLeadingComments( | ||
sourceFile: ts.SourceFile, node: ts.Node, lastCommentEnd: number): number { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to always return -1
- possible a bug? Missing a test?
src/transformer.ts
Outdated
const emittedFiles: string[] = []; | ||
const externs: {[fileName: string]: string} = {}; | ||
const modulesManifest = new ModulesManifest(); | ||
emitResults.forEach(er => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for/of
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/transformer_sourcemap.ts
Outdated
ts.setTextRange(node, originalNode ? originalNode : {pos: -1, end: -1}); | ||
ts.setOriginalNode(node, originalNode); | ||
|
||
// tslint:disable-next-line:no-any |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
FYI: this introduced 71 test failures on Windows. 😞 |
What kind? At 71 failures, it's most likely something trivial to fix?
|
Pretty much every comment is missing. Maybe I can find a simple solution. Example:
|
This PR introduces transformer support for tsickle.
The first 6 commits are prefactorings, which are used by the last commit which introduces the main API.
Global TAP presubmit is looking good using the new transformer API in G3.
Proposed way of landing this PR:
This way, we can have a bit of time between 2. and 3., without blocking other fixes for Tsickle. We are also able to rollback to using the old entry point without creating branches of Tsickle.