• 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                // check if at least one of alternative have moved scanner forward
349                if (tryConsumeDeclare() ||
350                    tryConsumeImport() ||
351                    tryConsumeExport() ||
352                    (detectJavaScriptImports && (
353                        tryConsumeRequireCall(/*skipCurrentToken*/ false, /*allowTemplateLiterals*/ true) ||
354                        tryConsumeDefine()
355                    ))) {
356                    continue;
357                }
358                else {
359                    nextToken();
360                }
361            }
362
363            scanner.setText(undefined);
364        }
365
366        if (readImportFiles) {
367            processImports();
368        }
369        processCommentPragmas(pragmaContext, sourceText);
370        processPragmasIntoFields(pragmaContext, noop);
371        if (externalModule) {
372            // for external modules module all nested ambient modules are augmentations
373            if (ambientExternalModules) {
374                // move all detected ambient modules to imported files since they need to be resolved
375                for (const decl of ambientExternalModules) {
376                    importedFiles.push(decl.ref);
377                }
378            }
379            return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined };
380        }
381        else {
382            // for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
383            let ambientModuleNames: string[] | undefined;
384            if (ambientExternalModules) {
385                for (const decl of ambientExternalModules) {
386                    if (decl.depth === 0) {
387                        if (!ambientModuleNames) {
388                            ambientModuleNames = [];
389                        }
390                        ambientModuleNames.push(decl.ref.fileName);
391                    }
392                    else {
393                        importedFiles.push(decl.ref);
394                    }
395                }
396            }
397            return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames };
398        }
399    }
400}
401