• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    describe("unittests:: customTransforms", () => {
3        function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers, options: CompilerOptions = {}) {
4            it(name, () => {
5                const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015));
6                const fileMap = arrayToMap(roots, file => file.fileName);
7                const outputs = new Map<string, string>();
8                const host: CompilerHost = {
9                    getSourceFile: (fileName) => fileMap.get(fileName),
10                    getDefaultLibFileName: () => "lib.d.ts",
11                    getCurrentDirectory: () => "",
12                    getDirectories: () => [],
13                    getCanonicalFileName: (fileName) => fileName,
14                    useCaseSensitiveFileNames: () => true,
15                    getNewLine: () => "\n",
16                    fileExists: (fileName) => fileMap.has(fileName),
17                    readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName)!.text : undefined,
18                    writeFile: (fileName, text) => outputs.set(fileName, text),
19                };
20
21                const program = createProgram(arrayFrom(fileMap.keys()), { newLine: NewLineKind.LineFeed, ...options }, host);
22                program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers);
23                let content = "";
24                for (const [file, text] of arrayFrom(outputs.entries())) {
25                    if (content) content += "\n\n";
26                    content += `// [${file}]\n`;
27                    content += text;
28                }
29                Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content);
30            });
31        }
32
33        const sources = [{
34            file: "source.ts",
35            text: `
36            function f1() { }
37            class c() { }
38            enum e { }
39            // leading
40            function f2() { } // trailing
41            `
42        }];
43
44        const before: TransformerFactory<SourceFile> = context => {
45            return file => visitEachChild(file, visit, context);
46            function visit(node: Node): VisitResult<Node> {
47                switch (node.kind) {
48                    case SyntaxKind.FunctionDeclaration:
49                        return visitFunction(node as FunctionDeclaration);
50                    default:
51                        return visitEachChild(node, visit, context);
52                }
53            }
54            function visitFunction(node: FunctionDeclaration) {
55                addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true);
56                return node;
57            }
58        };
59
60        const after: TransformerFactory<SourceFile> = context => {
61            return file => visitEachChild(file, visit, context);
62            function visit(node: Node): VisitResult<Node> {
63                switch (node.kind) {
64                    case SyntaxKind.VariableStatement:
65                        return visitVariableStatement(node as VariableStatement);
66                    default:
67                        return visitEachChild(node, visit, context);
68                }
69            }
70            function visitVariableStatement(node: VariableStatement) {
71                addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after");
72                return node;
73            }
74        };
75
76        emitsCorrectly("before", sources, { before: [before] });
77        emitsCorrectly("after", sources, { after: [after] });
78        emitsCorrectly("both", sources, { before: [before], after: [after] });
79
80        emitsCorrectly("before+decorators", [{
81            file: "source.ts",
82            text: `
83                declare const dec: any;
84                class B {}
85                @dec export class C { constructor(b: B) { } }
86                'change'
87            `
88        }], {before: [
89            context => node => visitNode(node, function visitor(node: Node): Node {
90                if (isStringLiteral(node) && node.text === "change") return factory.createStringLiteral("changed");
91                return visitEachChild(node, visitor, context);
92            })
93        ]}, {
94            target: ScriptTarget.ES5,
95            module: ModuleKind.ES2015,
96            emitDecoratorMetadata: true,
97            experimentalDecorators: true
98        });
99
100        emitsCorrectly("sourceMapExternalSourceFiles",
101            [
102                {
103                    file: "source.ts",
104                    // The text of length 'changed' is made to be on two lines so we know the line map change
105                    text: `\`multi
106                    line\`
107'change'`
108                },
109            ],
110            {
111                before: [
112                    context => node => visitNode(node, function visitor(node: Node): Node {
113                        if (isStringLiteral(node) && node.text === "change") {
114                            const text = "'changed'";
115                            const lineMap = computeLineStarts(text);
116                            setSourceMapRange(node, {
117                                pos: 0, end: text.length, source: {
118                                    text,
119                                    fileName: "another.html",
120                                    lineMap,
121                                    getLineAndCharacterOfPosition: pos => computeLineAndCharacterOfPosition(lineMap, pos)
122                                }
123                            });
124                            return node;
125                        }
126                        return visitEachChild(node, visitor, context);
127                    })
128                ]
129            },
130            { sourceMap: true }
131        );
132
133        emitsCorrectly("skipTriviaExternalSourceFiles",
134            [
135                {
136                    file: "source.ts",
137                    // The source file contains preceding trivia (e.g. whitespace) to try to confuse the `skipSourceTrivia` function.
138                    text: "         original;"
139                },
140            ],
141            {
142                before: [
143                    context => {
144                        const transformSourceFile: Transformer<SourceFile> = node => visitNode(node, function visitor(node: Node): Node {
145                            if (isIdentifier(node) && node.text === "original") {
146                                const newNode = factory.createIdentifier("changed");
147                                setSourceMapRange(newNode, {
148                                    pos: 0,
149                                    end: 7,
150                                    // Do not provide a custom skipTrivia function for `source`.
151                                    source: createSourceMapSource("another.html", "changed;")
152                                });
153                                return newNode;
154                            }
155                            return visitEachChild(node, visitor, context);
156                        });
157                        return {
158                            transformSourceFile,
159                            transformBundle: node => factory.createBundle(map(node.sourceFiles, transformSourceFile), node.prepends),
160                        };
161                    }
162                ]
163            },
164            { sourceMap: true, outFile: "source.js" }
165        );
166
167    });
168}
169