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

Commit d58950f

Browse files
committed
Add more docs (review feedback from @evmar)
1 parent 296e760 commit d58950f

11 files changed

+175
-47
lines changed

src/decorator-annotator.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ export class DecoratorClassVisitor {
133133
this.propDecorators.set(name, decorators);
134134
}
135135

136+
/**
137+
* For lowering decorators, we need to refer to constructor types.
138+
* So we start with the identifiers that represent these types.
139+
* However, TypeScript does not allow use to emit them in a value position
140+
* as it associated different symbol information with it.
141+
*
142+
* This method looks for the place where the value that is associated to
143+
* the type is defined and returns that identifier instead.
144+
*
145+
* @param typeSymbol
146+
* @return The identifier
147+
*/
136148
private getValueIdentifierForType(typeSymbol: ts.Symbol): ts.Identifier|null {
137149
if (!typeSymbol.valueDeclaration) {
138150
return null;
@@ -348,6 +360,7 @@ export function visitClassContentIncludingDecorators(
348360
classDecl: ts.ClassDeclaration, rewriter: Rewriter, decoratorVisitor?: DecoratorClassVisitor) {
349361
if (rewriter.file.text[classDecl.getEnd() - 1] !== '}') {
350362
rewriter.error(classDecl, 'unexpected class terminator');
363+
return;
351364
}
352365
rewriter.writeNodeFrom(classDecl, classDecl.getStart(), classDecl.getEnd() - 1);
353366
// At this point, we've emitted up through the final child of the class, so all that

src/rewriter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ export abstract class Rewriter {
8484
this.writeNodeFrom(node, pos);
8585
}
8686

87-
writeNodeFrom(node: ts.Node, pos: number, end = Number.MAX_SAFE_INTEGER) {
88-
if (node.getEnd() <= this.skipUpToOffset) {
87+
writeNodeFrom(node: ts.Node, pos: number, end = node.getEnd()) {
88+
if (end <= this.skipUpToOffset) {
8989
return;
9090
}
9191
const oldSkipUpToOffset = this.skipUpToOffset;
@@ -95,7 +95,7 @@ export abstract class Rewriter {
9595
this.visit(child);
9696
pos = child.getEnd();
9797
});
98-
this.writeRange(node, pos, Math.min(end, node.getEnd()));
98+
this.writeRange(node, pos, end);
9999
this.skipUpToOffset = oldSkipUpToOffset;
100100
}
101101

src/transformer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export function emitWithTsickle(
161161
});
162162
}
163163
// All diagnostics (including warnings) are treated as errors.
164-
// If we've decided to ignore them, just discard them.
164+
// If the host decides to ignore warnings, just discard them.
165165
// Warnings include stuff like "don't use @type in your jsdoc"; tsickle
166166
// warns and then fixes up the code to be Closure-compatible anyway.
167167
tsickleDiagnostics = tsickleDiagnostics.filter(

src/transformer_sourcemap.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import * as ts from 'typescript';
1111
import {SourceMapper, SourcePosition} from './source_map_utils';
1212
import {isTypeNodeKind, updateSourceFileNode, visitEachChild, visitNodeWithSynthesizedComments} from './transformer_util';
1313

14+
/**
15+
* @fileoverview Creates a TypeScript transformer that parses code into a new `ts.SourceFile`,
16+
* marks the nodes as synthetic and where possible maps the new nodes back to the original nodes
17+
* via sourcemap information.
18+
*/
1419
export function createTransformerFromSourceMap(
1520
operator: (sourceFile: ts.SourceFile, sourceMapper: SourceMapper) =>
1621
string): ts.TransformerFactory<ts.SourceFile> {
@@ -77,6 +82,10 @@ export function createTransformerFromSourceMap(
7782
};
7883
}
7984

85+
/**
86+
* Implementation of the `SourceMapper` that stores and retrieves mappings
87+
* to original nodes.
88+
*/
8089
class NodeSourceMapper implements SourceMapper {
8190
private originalNodeByGeneratedRange = new Map<string, ts.Node>();
8291
private genStartPositions = new Map<ts.Node, number>();

src/transformer_util.ts

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import * as tsickle from './tsickle';
1212
/**
1313
* Adjusts the given CustomTransformers with additional transformers
1414
* to fix bugs in TypeScript.
15-
* @param given A
1615
*/
1716
export function createCustomTransformers(given: ts.CustomTransformers): ts.CustomTransformers {
1817
if (!given.after && !given.before) {
@@ -51,7 +50,7 @@ function assertFileContext(context: TransformationContext, sourceFile: ts.Source
5150
}
5251

5352
/**
54-
* And extended version of the TransformationContext that stores the FileContext as well.
53+
* An extended version of the TransformationContext that stores the FileContext as well.
5554
*/
5655
interface TransformationContext extends ts.TransformationContext {
5756
fileContext?: FileContext;
@@ -74,6 +73,8 @@ class FileContext {
7473
* Transform that needs to be executed right before TypeScript's transform.
7574
*
7675
* This prepares the node tree to workaround some bugs in the TypeScript emitter.
76+
* TODO(tbosch): file issues with TypeScript, tracked in
77+
* https://github.com/angular/tsickle/issues/528.
7778
*/
7879
function preTypeScriptTransform(context: ts.TransformationContext) {
7980
return (sourceFile: ts.SourceFile) => {
@@ -178,7 +179,7 @@ function postTypescriptTransform(context: ts.TransformationContext) {
178179
} else if (
179180
parent3 && parent3.kind === ts.SyntaxKind.VariableStatement &&
180181
tsickle.hasModifierFlag(parent3, ts.ModifierFlags.Export)) {
181-
// TypeScript ignore synthetic comments on exported variables.
182+
// TypeScript ignores synthetic comments on exported variables.
182183
// find the parent ExpressionStatement like exports.foo = ...
183184
const expressionStmt =
184185
lastNodeWith(nodePath, (node) => node.kind === ts.SyntaxKind.ExpressionStatement);
@@ -188,7 +189,7 @@ function postTypescriptTransform(context: ts.TransformationContext) {
188189
}
189190
}
190191
}
191-
// TypeScript ignore synthetic comments on reexport / import statements.
192+
// TypeScript ignores synthetic comments on reexport / import statements.
192193
const moduleName = extractModuleNameFromRequireVariableStatement(node);
193194
if (moduleName && fileContext.importOrReexportDeclarations) {
194195
const index = importOrReexportsCount++;
@@ -240,8 +241,13 @@ function lastNodeWith(nodes: ts.Node[], predicate: (node: ts.Node) => boolean):
240241
}
241242

242243
/**
243-
* Synthesizes the comments before and after a node
244-
* befor calling a callback.
244+
* Convert comment text ranges before and after a node
245+
* into ts.SynthesizedComments for the node and prevent the
246+
* comment text ranges to be emitted, to allow
247+
* changing these comments.
248+
*
249+
* This function takes a visitor to be able to do some
250+
* state management after the caller is done changing a node.
245251
*/
246252
export function visitNodeWithSynthesizedComments<T extends ts.Node>(
247253
context: ts.TransformationContext, sourceFile: ts.SourceFile, node: T,
@@ -288,7 +294,8 @@ function resetNodeTextRangeToPreventDuplicateComments<T extends ts.Node>(node: T
288294
if (node.kind === ts.SyntaxKind.PropertyDeclaration) {
289295
allowTextRange = false;
290296
const pd = node as ts.Node as ts.PropertyDeclaration;
291-
// TODO(tbosch): Using pd.initializer! as the typescript typings for intializer are incorrect.
297+
// TODO(tbosch): Using pd.initializer! as the typescript typings for intializer are incorrect,
298+
// tracked in https://github.com/angular/tsickle/issues/528.
292299
node = ts.updateProperty(
293300
pd, pd.decorators, pd.modifiers, resetTextRange(pd.name) as ts.PropertyName, pd.type,
294301
pd.initializer!) as ts.Node as T;
@@ -311,6 +318,13 @@ function resetNodeTextRangeToPreventDuplicateComments<T extends ts.Node>(node: T
311318
}
312319
}
313320

321+
/**
322+
* Reads in the leading comment text ranges of the given node,
323+
* converts them into `ts.SyntheticComment`s and stores them on the node.
324+
*
325+
* @param lastCommentEnd The end of the last comment
326+
* @return The end of the last found comment, -1 if no comment was found.
327+
*/
314328
function synthesizeLeadingComments(
315329
sourceFile: ts.SourceFile, node: ts.Node, lastCommentEnd: number): number {
316330
const parent = node.parent;
@@ -329,6 +343,12 @@ function synthesizeLeadingComments(
329343
return -1;
330344
}
331345

346+
/**
347+
* Reads in the trailing comment text ranges of the given node,
348+
* converts them into `ts.SyntheticComment`s and stores them on the node.
349+
*
350+
* @return The end of the last found comment, -1 if no comment was found.
351+
*/
332352
function synthesizeTrailingComments(sourceFile: ts.SourceFile, node: ts.Node): number {
333353
const parent = node.parent;
334354
const sharesEndWithParent = parent && parent.kind !== ts.SyntaxKind.Block &&
@@ -344,6 +364,16 @@ function synthesizeTrailingComments(sourceFile: ts.SourceFile, node: ts.Node): n
344364
return -1;
345365
}
346366

367+
/**
368+
* Convert leading/trailing detached comment ranges of statement arrays
369+
* (e.g. the statements of a ts.SourceFile or ts.Block) into
370+
* `ts.NonEmittedStatement`s with `ts.SynthesizedComment`s and
371+
* prepends / appends them to the given statement array.
372+
* This is needed to allow changing these comments.
373+
*
374+
* This function takes a visitor to be able to do some
375+
* state management after the caller is done changing a node.
376+
*/
347377
function visitNodeStatementsWithSynthesizedComments<T extends ts.Node>(
348378
context: ts.TransformationContext, sourceFile: ts.SourceFile, node: T,
349379
statements: ts.NodeArray<ts.Statement>,
@@ -371,14 +401,22 @@ function visitNodeStatementsWithSynthesizedComments<T extends ts.Node>(
371401
return visitor(node, statements);
372402
}
373403

404+
/**
405+
* Convert leading detached comment ranges of statement arrays
406+
* (e.g. the statements of a ts.SourceFile or ts.Block) into a
407+
* `ts.NonEmittedStatement` with `ts.SynthesizedComment`s.
408+
*
409+
* A Detached leading comment is the first comment in a SourceFile / Block
410+
* that is separated with a newline from the first statement.
411+
*/
374412
function synthesizeDetachedLeadingComments(
375413
sourceFile: ts.SourceFile, node: ts.Node, statements: ts.NodeArray<ts.Statement>):
376414
{commentStmt: ts.Statement | null, lastCommentEnd: number} {
377415
let triviaEnd = statements.end;
378416
if (statements.length) {
379417
triviaEnd = statements[statements.length - 1].getStart();
380418
}
381-
const detachedComments = getDetachedStartingComments(sourceFile, statements.pos, triviaEnd);
419+
const detachedComments = getDetachedLeadingCommentRanges(sourceFile, statements.pos, triviaEnd);
382420
if (!detachedComments.length) {
383421
return {commentStmt: null, lastCommentEnd: -1};
384422
}
@@ -390,6 +428,14 @@ function synthesizeDetachedLeadingComments(
390428
return {commentStmt, lastCommentEnd};
391429
}
392430

431+
/**
432+
* Convert trailing detached comment ranges of statement arrays
433+
* (e.g. the statements of a ts.SourceFile or ts.Block) into a
434+
* `ts.NonEmittedStatement` with `ts.SynthesizedComment`s.
435+
*
436+
* A Detached trailing comment are all comments after the first newline
437+
* the follows the last statement in a SourceFile / Block.
438+
*/
393439
function synthesizeDetachedTrailingComments(
394440
sourceFile: ts.SourceFile, node: ts.Node, statements: ts.NodeArray<ts.Statement>):
395441
{commentStmt: ts.Statement | null, lastCommentEnd: number} {
@@ -413,8 +459,14 @@ function synthesizeDetachedTrailingComments(
413459
return {commentStmt, lastCommentEnd};
414460
}
415461

416-
// Adapted from compiler/comments.ts in TypeScript
417-
function getDetachedStartingComments(
462+
/**
463+
* Calculates the the detached leading comment ranges in an area of a SourceFile.
464+
* @param sourceFile The source file
465+
* @param start Where to start scanning
466+
* @param end Where to end scanning
467+
*/
468+
// Note: This code is based on compiler/comments.ts in TypeScript
469+
function getDetachedLeadingCommentRanges(
418470
sourceFile: ts.SourceFile, start: number, end: number): ts.CommentRange[] {
419471
const leadingComments = getAllLeadingCommentRanges(sourceFile, start, end);
420472
if (!leadingComments || !leadingComments.length) {
@@ -459,8 +511,13 @@ function getLineOfPos(sourceFile: ts.SourceFile, pos: number): number {
459511
return ts.getLineAndCharacterOfPosition(sourceFile, pos).line;
460512
}
461513

462-
463-
function synthesizeCommentRanges(sourceFile: ts.SourceFile, parsedComments: ts.CommentRange[]) {
514+
/**
515+
* Converts `ts.CommentRange`s into `ts.SynthesizedComment`s
516+
* @param sourceFile
517+
* @param parsedComments
518+
*/
519+
function synthesizeCommentRanges(
520+
sourceFile: ts.SourceFile, parsedComments: ts.CommentRange[]): ts.SynthesizedComment[] {
464521
const synthesizedComments: ts.SynthesizedComment[] = [];
465522
parsedComments.forEach(({kind, pos, end, hasTrailingNewLine}, commentIdx) => {
466523
let commentText = sourceFile.text.substring(pos, end).trim();
@@ -478,13 +535,25 @@ function synthesizeCommentRanges(sourceFile: ts.SourceFile, parsedComments: ts.C
478535
return synthesizedComments;
479536
}
480537

538+
/**
539+
* Creates a non emitted statement that can be used to store synthesized comments.
540+
*/
481541
function createNotEmittedStatement(sourceFile: ts.SourceFile) {
482542
const stmt = ts.createNotEmittedStatement(sourceFile);
483543
ts.setOriginalNode(stmt, undefined);
484544
ts.setTextRange(stmt, {pos: 0, end: 0});
485545
return stmt;
486546
}
487547

548+
/**
549+
* Returns the leading comment ranges in the source file that start at the given position.
550+
* This is the same as `ts.getLeadingCommentRanges`, except that it does not skip
551+
* comments before the first newline in the range.
552+
*
553+
* @param sourceFile
554+
* @param start Where to start scanning
555+
* @param end Where to end scanning
556+
*/
488557
function getAllLeadingCommentRanges(
489558
sourceFile: ts.SourceFile, start: number, end: number): ts.CommentRange[] {
490559
// exeute ts.getLeadingCommentRanges with pos = 0 so that it does not skip
@@ -498,6 +567,15 @@ function getAllLeadingCommentRanges(
498567
}));
499568
}
500569

570+
/**
571+
* This is a version of `ts.updateSourceFileNode` that prevents some
572+
* bugs in TypeScript.
573+
* TODO(tbosch): file an issue with TypeScript, tracked in
574+
* https://github.com/angular/tsickle/issues/528.
575+
*
576+
* @param sf
577+
* @param statements
578+
*/
501579
export function updateSourceFileNode(
502580
sf: ts.SourceFile, statements: ts.NodeArray<ts.Statement>): ts.SourceFile {
503581
if (statements === sf.statements) {
@@ -522,6 +600,12 @@ export function getMutableClone<T extends ts.Node>(node: T): T {
522600
return clone;
523601
}
524602

603+
/**
604+
* This is a version of `ts.visitEachChild` that does not visit children of types
605+
* to prevent errors from TypeScript.
606+
* TODO(tbosch): file an issue with TypeScript, tracked in
607+
* https://github.com/angular/tsickle/issues/528.
608+
*/
525609
export function visitEachChild<T extends ts.Node>(
526610
node: T, visitor: ts.Visitor, context: ts.TransformationContext): T {
527611
// Don't visit children of types,

0 commit comments

Comments
 (0)