• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    export function preProcessFile(sourceText: string, readImportFiles = true, detectJavaScriptImports = false): PreProcessedFileInfo {
3        const pragmaContext: PragmaContext = {
4            languageVersion: ScriptTarget.ES5, // controls whether the token scanner considers unicode identifiers or not - shouldn't matter, since we're only using it for trivia
5            pragmas: undefined,
6            checkJsDirective: undefined,
7            referencedFiles: [],
8            typeReferenceDirectives: [],
9            libReferenceDirectives: [],
10            amdDependencies: [],
11            hasNoDefaultLib: undefined,
12            moduleName: undefined
13        };
14        const importedFiles: FileReference[] = [];
15        let ambientExternalModules: { ref: FileReference, depth: number }[] | undefined;
16        let lastToken: SyntaxKind;
17        let currentToken: SyntaxKind;
18        let braceNesting = 0;
19        // assume that text represent an external module if it contains at least one top level import/export
20        // ambient modules that are found inside external modules are interpreted as module augmentations
21        let externalModule = false;
22
23        function nextToken() {
24            lastToken = currentToken;
25            currentToken = scanner.scan();
26            if (currentToken === SyntaxKind.OpenBraceToken) {
27                braceNesting++;
28            }
29            else if (currentToken === SyntaxKind.CloseBraceToken) {
30                braceNesting--;
31            }
32            return currentToken;
33        }
34
35        function getFileReference() {
36            const fileName = scanner.getTokenValue();
37            const pos = scanner.getTokenPos();
38            return { fileName, pos, end: pos + fileName.length };
39        }
40
41        function recordAmbientExternalModule(): void {
42            if (!ambientExternalModules) {
43                ambientExternalModules = [];
44            }
45            ambientExternalModules.push({ ref: getFileReference(), depth: braceNesting });
46        }
47
48        function recordModuleName() {
49            importedFiles.push(getFileReference());
50
51            markAsExternalModuleIfTopLevel();
52        }
53
54        function markAsExternalModuleIfTopLevel() {
55            if (braceNesting === 0) {
56                externalModule = true;
57            }
58        }
59
60        /**
61         * Returns true if at least one token was consumed from the stream
62         */
63        function tryConsumeDeclare(): boolean {
64            let token = scanner.getToken();
65            if (token === SyntaxKind.DeclareKeyword) {
66                // declare module "mod"
67                token = nextToken();
68                if (token === SyntaxKind.ModuleKeyword) {
69                    token = nextToken();
70                    if (token === SyntaxKind.StringLiteral) {
71                        recordAmbientExternalModule();
72                    }
73                }
74                return true;
75            }
76
77            return false;
78        }
79
80        /**
81         * Returns true if at least one token was consumed from the stream
82         */
83        function tryConsumeImport(): boolean {
84            if (lastToken === SyntaxKind.DotToken) {
85                return false;
86            }
87            let token = scanner.getToken();
88            if (token === SyntaxKind.ImportKeyword) {
89                token = nextToken();
90                if (token === SyntaxKind.OpenParenToken) {
91                    token = nextToken();
92                    if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) {
93                        // import("mod");
94                        recordModuleName();
95                        return true;
96                    }
97                }
98                else if (token === SyntaxKind.StringLiteral) {
99                    // import "mod";
100                    recordModuleName();
101                    return true;
102                }
103                else {
104                    if (token === SyntaxKind.TypeKeyword) {
105                        const skipTypeKeyword = scanner.lookAhead(() => {
106                            const token = scanner.scan();
107                            return token !== SyntaxKind.FromKeyword && (
108                                token === SyntaxKind.AsteriskToken ||
109                                token === SyntaxKind.OpenBraceToken ||
110                                token === SyntaxKind.Identifier ||
111                                isKeyword(token)
112                            );
113                        });
114                        if (skipTypeKeyword) {
115                            token = nextToken();
116                        }
117                    }
118
119                    if (token === SyntaxKind.Identifier || isKeyword(token)) {
120                        token = nextToken();
121                        if (token === SyntaxKind.FromKeyword) {
122                            token = nextToken();
123                            if (token === SyntaxKind.StringLiteral) {
124                                // import d from "mod";
125                                recordModuleName();
126                                return true;
127                            }
128                        }
129                        else if (token === SyntaxKind.EqualsToken) {
130                            if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
131                                return true;
132                            }
133                        }
134                        else if (token === SyntaxKind.CommaToken) {
135                            // consume comma and keep going
136                            token = nextToken();
137                        }
138                        else {
139                            // unknown syntax
140                            return true;
141                        }
142                    }
143
144                    if (token === SyntaxKind.OpenBraceToken) {
145                        token = nextToken();
146                        // consume "{ a as B, c, d as D}" clauses
147                        // make sure that it stops on EOF
148                        while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
149                            token = nextToken();
150                        }
151
152                        if (token === SyntaxKind.CloseBraceToken) {
153                            token = nextToken();
154                            if (token === SyntaxKind.FromKeyword) {
155                                token = nextToken();
156                                if (token === SyntaxKind.StringLiteral) {
157                                    // import {a as A} from "mod";
158                                    // import d, {a, b as B} from "mod"
159                                    recordModuleName();
160                                }
161                            }
162                        }
163                    }
164                    else if (token === SyntaxKind.AsteriskToken) {
165                        token = nextToken();
166                        if (token === SyntaxKind.AsKeyword) {
167                            token = nextToken();
168                            if (token === SyntaxKind.Identifier || isKeyword(token)) {
169                                token = nextToken();
170                                if (token === SyntaxKind.FromKeyword) {
171                                    token = nextToken();
172                                    if (token === SyntaxKind.StringLiteral) {
173                                        // import * as NS from "mod"
174                                        // import d, * as NS from "mod"
175                                        recordModuleName();
176                                    }
177                                }
178                            }
179                        }
180                    }
181                }
182
183                return true;
184            }
185
186            return false;
187        }
188
189        function tryConsumeExport(): boolean {
190            let token = scanner.getToken();
191            if (token === SyntaxKind.ExportKeyword) {
192                markAsExternalModuleIfTopLevel();
193                token = nextToken();
194                if (token === SyntaxKind.TypeKeyword) {
195                    const skipTypeKeyword = scanner.lookAhead(() => {
196                        const token = scanner.scan();
197                        return token === SyntaxKind.AsteriskToken ||
198                            token === SyntaxKind.OpenBraceToken;
199                    });
200                    if (skipTypeKeyword) {
201                        token = nextToken();
202                    }
203                }
204                if (token === SyntaxKind.OpenBraceToken) {
205                    token = nextToken();
206                    // consume "{ a as B, c, d as D}" clauses
207                    // make sure it stops on EOF
208                    while (token !== SyntaxKind.CloseBraceToken && token !== SyntaxKind.EndOfFileToken) {
209                        token = nextToken();
210                    }
211
212                    if (token === SyntaxKind.CloseBraceToken) {
213                        token = nextToken();
214                        if (token === SyntaxKind.FromKeyword) {
215                            token = nextToken();
216                            if (token === SyntaxKind.StringLiteral) {
217                                // export {a as A} from "mod";
218                                // export {a, b as B} from "mod"
219                                recordModuleName();
220                            }
221                        }
222                    }
223                }
224                else if (token === SyntaxKind.AsteriskToken) {
225                    token = nextToken();
226                    if (token === SyntaxKind.FromKeyword) {
227                        token = nextToken();
228                        if (token === SyntaxKind.StringLiteral) {
229                            // export * from "mod"
230                            recordModuleName();
231                        }
232                    }
233                }
234                else if (token === SyntaxKind.ImportKeyword) {
235                    token = nextToken();
236                    if (token === SyntaxKind.TypeKeyword) {
237                        const skipTypeKeyword = scanner.lookAhead(() => {
238                            const token = scanner.scan();
239                            return token === SyntaxKind.Identifier ||
240                                isKeyword(token);
241                        });
242                        if (skipTypeKeyword) {
243                            token = nextToken();
244                        }
245                    }
246                    if (token === SyntaxKind.Identifier || isKeyword(token)) {
247                        token = nextToken();
248                        if (token === SyntaxKind.EqualsToken) {
249                            if (tryConsumeRequireCall(/*skipCurrentToken*/ true)) {
250                                return true;
251                            }
252                        }
253                    }
254                }
255
256                return true;
257            }
258
259            return false;
260        }
261
262        function tryConsumeRequireCall(skipCurrentToken: boolean, allowTemplateLiterals = false): boolean {
263            let token = skipCurrentToken ? nextToken() : scanner.getToken();
264            if (token === SyntaxKind.RequireKeyword) {
265                token = nextToken();
266                if (token === SyntaxKind.OpenParenToken) {
267                    token = nextToken();
268                    if (token === SyntaxKind.StringLiteral ||
269                        allowTemplateLiterals && token === SyntaxKind.NoSubstitutionTemplateLiteral) {
270                        //  require("mod");
271                        recordModuleName();
272                    }
273                }
274                return true;
275            }
276            return false;
277        }
278
279        function tryConsumeDefine(): boolean {
280            let token = scanner.getToken();
281            if (token === SyntaxKind.Identifier && scanner.getTokenValue() === "define") {
282                token = nextToken();
283                if (token !== SyntaxKind.OpenParenToken) {
284                    return true;
285                }
286
287                token = nextToken();
288                if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) {
289                    // looks like define ("modname", ... - skip string literal and comma
290                    token = nextToken();
291                    if (token === SyntaxKind.CommaToken) {
292                        token = nextToken();
293                    }
294                    else {
295                        // unexpected token
296                        return true;
297                    }
298                }
299
300                // should be start of dependency list
301                if (token !== SyntaxKind.OpenBracketToken) {
302                    return true;
303                }
304
305                // skip open bracket
306                token = nextToken();
307                // scan until ']' or EOF
308                while (token !== SyntaxKind.CloseBracketToken && token !== SyntaxKind.EndOfFileToken) {
309                    // record string literals as module names
310                    if (token === SyntaxKind.StringLiteral || token === SyntaxKind.NoSubstitutionTemplateLiteral) {
311                        recordModuleName();
312                    }
313
314                    token = nextToken();
315                }
316                return true;
317
318            }
319            return false;
320        }
321
322        function processImports(): void {
323            scanner.setText(sourceText);
324            nextToken();
325            // Look for:
326            //    import "mod";
327            //    import d from "mod"
328            //    import {a as A } from "mod";
329            //    import * as NS from "mod"
330            //    import d, {a, b as B} from "mod"
331            //    import i = require("mod");
332            //    import("mod");
333
334            //    export * from "mod"
335            //    export {a as b} from "mod"
336            //    export import i = require("mod")
337            //    (for JavaScript files) require("mod")
338
339            // Do not look for:
340            //    AnySymbol.import("mod")
341            //    AnySymbol.nested.import("mod")
342
343            while (true) {
344                if (scanner.getToken() === SyntaxKind.EndOfFileToken) {
345                    break;
346                }
347
348                if (scanner.getToken() === SyntaxKind.TemplateHead) {
349                    const stack = [scanner.getToken()];
350                    loop: while (length(stack)) {
351                        const token = scanner.scan();
352                        switch (token) {
353                            case SyntaxKind.EndOfFileToken:
354                                break loop;
355                            case SyntaxKind.ImportKeyword:
356                                tryConsumeImport();
357                                break;
358                            case SyntaxKind.TemplateHead:
359                                stack.push(token);
360                                break;
361                            case SyntaxKind.OpenBraceToken:
362                                if (length(stack)) {
363                                    stack.push(token);
364                                }
365                                break;
366                            case SyntaxKind.CloseBraceToken:
367                                if (length(stack)) {
368                                    if (lastOrUndefined(stack) === SyntaxKind.TemplateHead) {
369                                        if (scanner.reScanTemplateToken(/* isTaggedTemplate */ false) === SyntaxKind.TemplateTail) {
370                                            stack.pop();
371                                        }
372                                    }
373                                    else {
374                                        stack.pop();
375                                    }
376                                }
377                                break;
378                        }
379                    }
380                    nextToken();
381                }
382
383                // check if at least one of alternative have moved scanner forward
384                if (tryConsumeDeclare() ||
385                    tryConsumeImport() ||
386                    tryConsumeExport() ||
387                    (detectJavaScriptImports && (
388                        tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) ||
389                        tryConsumeDefine()
390                    ))) {
391                    continue;
392                }
393                else {
394                    nextToken();
395                }
396            }
397
398            scanner.setText(undefined);
399        }
400
401        if (readImportFiles) {
402            processImports();
403        }
404        processCommentPragmas(pragmaContext, sourceText);
405        processPragmasIntoFields(pragmaContext, noop);
406        if (externalModule) {
407            // for external modules module all nested ambient modules are augmentations
408            if (ambientExternalModules) {
409                // move all detected ambient modules to imported files since they need to be resolved
410                for (const decl of ambientExternalModules) {
411                    importedFiles.push(decl.ref);
412                }
413            }
414            return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined };
415        }
416        else {
417            // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
418            let ambientModuleNames: string[] | undefined;
419            if (ambientExternalModules) {
420                for (const decl of ambientExternalModules) {
421                    if (decl.depth === 0) {
422                        if (!ambientModuleNames) {
423                            ambientModuleNames = [];
424                        }
425                        ambientModuleNames.push(decl.ref.fileName);
426                    }
427                    else {
428                        importedFiles.push(decl.ref);
429                    }
430                }
431            }
432            return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames };
433        }
434    }
435}
436