@@ -60,6 +60,41 @@ namespace ts {
6060 text . charCodeAt ( start + 3 ) !== CharacterCodes . slash ;
6161 }
6262
63+ /*@internal */
64+ export function isFileProbablyExternalModule ( sourceFile : SourceFile ) {
65+ // Try to use the first top-level import/export when available, then
66+ // fall back to looking for an 'import.meta' somewhere in the tree if necessary.
67+ return forEach ( sourceFile . statements , isAnExternalModuleIndicatorNode ) ||
68+ getImportMetaIfNecessary ( sourceFile ) ;
69+ }
70+
71+ function isAnExternalModuleIndicatorNode ( node : Node ) {
72+ return hasModifierOfKind ( node , SyntaxKind . ExportKeyword )
73+ || isImportEqualsDeclaration ( node ) && isExternalModuleReference ( node . moduleReference )
74+ || isImportDeclaration ( node )
75+ || isExportAssignment ( node )
76+ || isExportDeclaration ( node ) ? node : undefined ;
77+ }
78+
79+ function getImportMetaIfNecessary ( sourceFile : SourceFile ) {
80+ return sourceFile . flags & NodeFlags . PossiblyContainsImportMeta ?
81+ walkTreeForImportMeta ( sourceFile ) :
82+ undefined ;
83+ }
84+
85+ function walkTreeForImportMeta ( node : Node ) : Node | undefined {
86+ return isImportMeta ( node ) ? node : forEachChild ( node , walkTreeForImportMeta ) ;
87+ }
88+
89+ /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */
90+ function hasModifierOfKind ( node : Node , kind : SyntaxKind ) {
91+ return some ( node . modifiers , m => m . kind === kind ) ;
92+ }
93+
94+ function isImportMeta ( node : Node ) : boolean {
95+ return isMetaProperty ( node ) && node . keywordToken === SyntaxKind . ImportKeyword && node . name . escapedText === "meta" ;
96+ }
97+
6398 /**
6499 * Invokes a callback for each child of the given node. The 'cbNode' callback is invoked for all child nodes
65100 * stored in properties. If a 'cbNodes' callback is specified, it is invoked for embedded arrays; otherwise,
@@ -642,17 +677,46 @@ namespace ts {
642677 }
643678 }
644679
645- export function createSourceFile ( fileName : string , sourceText : string , languageVersion : ScriptTarget , setParentNodes = false , scriptKind ?: ScriptKind ) : SourceFile {
680+ export interface CreateSourceFileOptions {
681+ languageVersion : ScriptTarget ;
682+ /**
683+ * Controls the format the file is detected as - this can be derived from only the path
684+ * and files on disk, but needs to be done with a module resolution cache in scope to be performant.
685+ * This is usually `undefined` for compilations that do not have `moduleResolution` values of `node12` or `nodenext`.
686+ */
687+ impliedNodeFormat ?: ModuleKind . ESNext | ModuleKind . CommonJS ;
688+ /**
689+ * Controls how module-y-ness is set for the given file. Usually the result of calling
690+ * `getSetExternalModuleIndicator` on a valid `CompilerOptions` object. If not present, the default
691+ * check specified by `isFileProbablyExternalModule` will be used to set the field.
692+ */
693+ setExternalModuleIndicator ?: ( file : SourceFile ) => void ;
694+ }
695+
696+ function setExternalModuleIndicator ( sourceFile : SourceFile ) {
697+ sourceFile . externalModuleIndicator = isFileProbablyExternalModule ( sourceFile ) ;
698+ }
699+
700+ export function createSourceFile ( fileName : string , sourceText : string , languageVersionOrOptions : ScriptTarget | CreateSourceFileOptions , setParentNodes = false , scriptKind ?: ScriptKind ) : SourceFile {
646701 tracing ?. push ( tracing . Phase . Parse , "createSourceFile" , { path : fileName } , /*separateBeginAndEnd*/ true ) ;
647702 performance . mark ( "beforeParse" ) ;
648703 let result : SourceFile ;
649704
650705 perfLogger . logStartParseSourceFile ( fileName ) ;
706+ const {
707+ languageVersion,
708+ setExternalModuleIndicator : overrideSetExternalModuleIndicator ,
709+ impliedNodeFormat : format
710+ } = typeof languageVersionOrOptions === "object" ? languageVersionOrOptions : ( { languageVersion : languageVersionOrOptions } as CreateSourceFileOptions ) ;
651711 if ( languageVersion === ScriptTarget . JSON ) {
652- result = Parser . parseSourceFile ( fileName , sourceText , languageVersion , /*syntaxCursor*/ undefined , setParentNodes , ScriptKind . JSON ) ;
712+ result = Parser . parseSourceFile ( fileName , sourceText , languageVersion , /*syntaxCursor*/ undefined , setParentNodes , ScriptKind . JSON , noop ) ;
653713 }
654714 else {
655- result = Parser . parseSourceFile ( fileName , sourceText , languageVersion , /*syntaxCursor*/ undefined , setParentNodes , scriptKind ) ;
715+ const setIndicator = format === undefined ? overrideSetExternalModuleIndicator : ( file : SourceFile ) => {
716+ file . impliedNodeFormat = format ;
717+ return ( overrideSetExternalModuleIndicator || setExternalModuleIndicator ) ( file ) ;
718+ } ;
719+ result = Parser . parseSourceFile ( fileName , sourceText , languageVersion , /*syntaxCursor*/ undefined , setParentNodes , scriptKind , setIndicator ) ;
656720 }
657721 perfLogger . logStopParseSourceFile ( ) ;
658722
@@ -851,7 +915,7 @@ namespace ts {
851915 // attached to the EOF token.
852916 let parseErrorBeforeNextFinishedNode = false ;
853917
854- export function parseSourceFile ( fileName : string , sourceText : string , languageVersion : ScriptTarget , syntaxCursor : IncrementalParser . SyntaxCursor | undefined , setParentNodes = false , scriptKind ?: ScriptKind ) : SourceFile {
918+ export function parseSourceFile ( fileName : string , sourceText : string , languageVersion : ScriptTarget , syntaxCursor : IncrementalParser . SyntaxCursor | undefined , setParentNodes = false , scriptKind ?: ScriptKind , setExternalModuleIndicatorOverride ?: ( file : SourceFile ) => void ) : SourceFile {
855919 scriptKind = ensureScriptKind ( fileName , scriptKind ) ;
856920 if ( scriptKind === ScriptKind . JSON ) {
857921 const result = parseJsonText ( fileName , sourceText , languageVersion , syntaxCursor , setParentNodes ) ;
@@ -867,7 +931,7 @@ namespace ts {
867931
868932 initializeState ( fileName , sourceText , languageVersion , syntaxCursor , scriptKind ) ;
869933
870- const result = parseSourceFileWorker ( languageVersion , setParentNodes , scriptKind ) ;
934+ const result = parseSourceFileWorker ( languageVersion , setParentNodes , scriptKind , setExternalModuleIndicatorOverride || setExternalModuleIndicator ) ;
871935
872936 clearState ( ) ;
873937
@@ -955,7 +1019,7 @@ namespace ts {
9551019 }
9561020
9571021 // Set source file so that errors will be reported with this file name
958- const sourceFile = createSourceFile ( fileName , ScriptTarget . ES2015 , ScriptKind . JSON , /*isDeclaration*/ false , statements , endOfFileToken , sourceFlags ) ;
1022+ const sourceFile = createSourceFile ( fileName , ScriptTarget . ES2015 , ScriptKind . JSON , /*isDeclaration*/ false , statements , endOfFileToken , sourceFlags , noop ) ;
9591023
9601024 if ( setParentNodes ) {
9611025 fixupParentReferences ( sourceFile ) ;
@@ -1039,7 +1103,7 @@ namespace ts {
10391103 topLevel = true ;
10401104 }
10411105
1042- function parseSourceFileWorker ( languageVersion : ScriptTarget , setParentNodes : boolean , scriptKind : ScriptKind ) : SourceFile {
1106+ function parseSourceFileWorker ( languageVersion : ScriptTarget , setParentNodes : boolean , scriptKind : ScriptKind , setExternalModuleIndicator : ( file : SourceFile ) => void ) : SourceFile {
10431107 const isDeclarationFile = isDeclarationFileName ( fileName ) ;
10441108 if ( isDeclarationFile ) {
10451109 contextFlags |= NodeFlags . Ambient ;
@@ -1054,7 +1118,7 @@ namespace ts {
10541118 Debug . assert ( token ( ) === SyntaxKind . EndOfFileToken ) ;
10551119 const endOfFileToken = addJSDocComment ( parseTokenNode < EndOfFileToken > ( ) ) ;
10561120
1057- const sourceFile = createSourceFile ( fileName , languageVersion , scriptKind , isDeclarationFile , statements , endOfFileToken , sourceFlags ) ;
1121+ const sourceFile = createSourceFile ( fileName , languageVersion , scriptKind , isDeclarationFile , statements , endOfFileToken , sourceFlags , setExternalModuleIndicator ) ;
10581122
10591123 // A member of ReadonlyArray<T> isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future
10601124 processCommentPragmas ( sourceFile as { } as PragmaContext , sourceText ) ;
@@ -1213,28 +1277,42 @@ namespace ts {
12131277 setParentRecursive ( rootNode , /*incremental*/ true ) ;
12141278 }
12151279
1216- function createSourceFile ( fileName : string , languageVersion : ScriptTarget , scriptKind : ScriptKind , isDeclarationFile : boolean , statements : readonly Statement [ ] , endOfFileToken : EndOfFileToken , flags : NodeFlags ) : SourceFile {
1280+ function createSourceFile (
1281+ fileName : string ,
1282+ languageVersion : ScriptTarget ,
1283+ scriptKind : ScriptKind ,
1284+ isDeclarationFile : boolean ,
1285+ statements : readonly Statement [ ] ,
1286+ endOfFileToken : EndOfFileToken ,
1287+ flags : NodeFlags ,
1288+ setExternalModuleIndicator : ( sourceFile : SourceFile ) => void ) : SourceFile {
12171289 // code from createNode is inlined here so createNode won't have to deal with special case of creating source files
12181290 // this is quite rare comparing to other nodes and createNode should be as fast as possible
12191291 let sourceFile = factory . createSourceFile ( statements , endOfFileToken , flags ) ;
12201292 setTextRangePosWidth ( sourceFile , 0 , sourceText . length ) ;
1221- setExternalModuleIndicator ( sourceFile ) ;
1293+ setFields ( sourceFile ) ;
12221294
12231295 // If we parsed this as an external module, it may contain top-level await
12241296 if ( ! isDeclarationFile && isExternalModule ( sourceFile ) && sourceFile . transformFlags & TransformFlags . ContainsPossibleTopLevelAwait ) {
12251297 sourceFile = reparseTopLevelAwait ( sourceFile ) ;
1298+ setFields ( sourceFile ) ;
12261299 }
12271300
1228- sourceFile . text = sourceText ;
1229- sourceFile . bindDiagnostics = [ ] ;
1230- sourceFile . bindSuggestionDiagnostics = undefined ;
1231- sourceFile . languageVersion = languageVersion ;
1232- sourceFile . fileName = fileName ;
1233- sourceFile . languageVariant = getLanguageVariant ( scriptKind ) ;
1234- sourceFile . isDeclarationFile = isDeclarationFile ;
1235- sourceFile . scriptKind = scriptKind ;
1236-
12371301 return sourceFile ;
1302+
1303+ function setFields ( sourceFile : SourceFile ) {
1304+ sourceFile . text = sourceText ;
1305+ sourceFile . bindDiagnostics = [ ] ;
1306+ sourceFile . bindSuggestionDiagnostics = undefined ;
1307+ sourceFile . languageVersion = languageVersion ;
1308+ sourceFile . fileName = fileName ;
1309+ sourceFile . languageVariant = getLanguageVariant ( scriptKind ) ;
1310+ sourceFile . isDeclarationFile = isDeclarationFile ;
1311+ sourceFile . scriptKind = scriptKind ;
1312+
1313+ setExternalModuleIndicator ( sourceFile ) ;
1314+ sourceFile . setExternalModuleIndicator = setExternalModuleIndicator ;
1315+ }
12381316 }
12391317
12401318 function setContextFlag ( val : boolean , flag : NodeFlags ) {
@@ -7575,41 +7653,6 @@ namespace ts {
75757653 return withJSDoc ( finishNode ( node , pos ) , hasJSDoc ) ;
75767654 }
75777655
7578- function setExternalModuleIndicator ( sourceFile : SourceFile ) {
7579- // Try to use the first top-level import/export when available, then
7580- // fall back to looking for an 'import.meta' somewhere in the tree if necessary.
7581- sourceFile . externalModuleIndicator =
7582- forEach ( sourceFile . statements , isAnExternalModuleIndicatorNode ) ||
7583- getImportMetaIfNecessary ( sourceFile ) ;
7584- }
7585-
7586- function isAnExternalModuleIndicatorNode ( node : Node ) {
7587- return hasModifierOfKind ( node , SyntaxKind . ExportKeyword )
7588- || isImportEqualsDeclaration ( node ) && ts . isExternalModuleReference ( node . moduleReference )
7589- || isImportDeclaration ( node )
7590- || isExportAssignment ( node )
7591- || isExportDeclaration ( node ) ? node : undefined ;
7592- }
7593-
7594- function getImportMetaIfNecessary ( sourceFile : SourceFile ) {
7595- return sourceFile . flags & NodeFlags . PossiblyContainsImportMeta ?
7596- walkTreeForExternalModuleIndicators ( sourceFile ) :
7597- undefined ;
7598- }
7599-
7600- function walkTreeForExternalModuleIndicators ( node : Node ) : Node | undefined {
7601- return isImportMeta ( node ) ? node : forEachChild ( node , walkTreeForExternalModuleIndicators ) ;
7602- }
7603-
7604- /** Do not use hasModifier inside the parser; it relies on parent pointers. Use this instead. */
7605- function hasModifierOfKind ( node : Node , kind : SyntaxKind ) {
7606- return some ( node . modifiers , m => m . kind === kind ) ;
7607- }
7608-
7609- function isImportMeta ( node : Node ) : boolean {
7610- return isMetaProperty ( node ) && node . keywordToken === SyntaxKind . ImportKeyword && node . name . escapedText === "meta" ;
7611- }
7612-
76137656 const enum ParsingContext {
76147657 SourceElements , // Elements in source file
76157658 BlockStatements , // Statements in block
@@ -7652,7 +7695,7 @@ namespace ts {
76527695 currentToken = scanner . scan ( ) ;
76537696 const jsDocTypeExpression = parseJSDocTypeExpression ( ) ;
76547697
7655- const sourceFile = createSourceFile ( "file.js" , ScriptTarget . Latest , ScriptKind . JS , /*isDeclarationFile*/ false , [ ] , factory . createToken ( SyntaxKind . EndOfFileToken ) , NodeFlags . None ) ;
7698+ const sourceFile = createSourceFile ( "file.js" , ScriptTarget . Latest , ScriptKind . JS , /*isDeclarationFile*/ false , [ ] , factory . createToken ( SyntaxKind . EndOfFileToken ) , NodeFlags . None , noop ) ;
76567699 const diagnostics = attachFileToDiagnostics ( parseDiagnostics , sourceFile ) ;
76577700 if ( jsDocDiagnostics ) {
76587701 sourceFile . jsDocDiagnostics = attachFileToDiagnostics ( jsDocDiagnostics , sourceFile ) ;
@@ -8698,7 +8741,7 @@ namespace ts {
86988741 if ( sourceFile . statements . length === 0 ) {
86998742 // If we don't have any statements in the current source file, then there's no real
87008743 // way to incrementally parse. So just do a full parse instead.
8701- return Parser . parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , /*syntaxCursor*/ undefined , /*setParentNodes*/ true , sourceFile . scriptKind ) ;
8744+ return Parser . parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , /*syntaxCursor*/ undefined , /*setParentNodes*/ true , sourceFile . scriptKind , sourceFile . setExternalModuleIndicator ) ;
87028745 }
87038746
87048747 // Make sure we're not trying to incrementally update a source file more than once. Once
@@ -8762,7 +8805,7 @@ namespace ts {
87628805 // inconsistent tree. Setting the parents on the new tree should be very fast. We
87638806 // will immediately bail out of walking any subtrees when we can see that their parents
87648807 // are already correct.
8765- const result = Parser . parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , syntaxCursor , /*setParentNodes*/ true , sourceFile . scriptKind ) ;
8808+ const result = Parser . parseSourceFile ( sourceFile . fileName , newText , sourceFile . languageVersion , syntaxCursor , /*setParentNodes*/ true , sourceFile . scriptKind , sourceFile . setExternalModuleIndicator ) ;
87668809 result . commentDirectives = getNewCommentDirectives (
87678810 sourceFile . commentDirectives ,
87688811 result . commentDirectives ,
0 commit comments