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