-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Use regex+getTokenAtPosition to find dynamic import #28104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1895,12 +1895,9 @@ namespace ts { | |
|
||
for (const node of file.statements) { | ||
collectModuleReferences(node, /*inAmbientModule*/ false); | ||
if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { | ||
collectDynamicImportOrRequireCalls(node); | ||
} | ||
} | ||
if ((file.flags & NodeFlags.PossiblyContainsDynamicImport) || isJavaScriptFile) { | ||
collectDynamicImportOrRequireCalls(file.endOfFileToken); | ||
collectDynamicImportOrRequireCalls(file); | ||
} | ||
|
||
file.imports = imports || emptyArray; | ||
|
@@ -1952,25 +1949,38 @@ namespace ts { | |
} | ||
} | ||
|
||
function collectDynamicImportOrRequireCalls(node: Node): void { | ||
if (isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { | ||
imports = append(imports, node.arguments[0]); | ||
} | ||
// we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. | ||
else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { | ||
imports = append(imports, node.arguments[0] as StringLiteralLike); | ||
} | ||
else if (isLiteralImportTypeNode(node)) { | ||
imports = append(imports, node.argument.literal); | ||
} | ||
collectDynamicImportOrRequireCallsForEachChild(node); | ||
if (hasJSDocNodes(node)) { | ||
forEach(node.jsDoc, collectDynamicImportOrRequireCallsForEachChild); | ||
function collectDynamicImportOrRequireCalls(file: SourceFile) { | ||
const r = /import|require/g; | ||
while (r.exec(file.text) !== null) { | ||
const node = getTokenAtPosition(file, r.lastIndex); | ||
sandersn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (isRequireCall(node, /*checkArgumentIsStringLiteralLike*/ true)) { | ||
imports = append(imports, node.arguments[0]); | ||
} | ||
// we have to check the argument list has length of 1. We will still have to process these even though we have parsing error. | ||
else if (isImportCall(node) && node.arguments.length === 1 && isStringLiteralLike(node.arguments[0])) { | ||
imports = append(imports, node.arguments[0] as StringLiteralLike); | ||
} | ||
else if (isLiteralImportTypeNode(node)) { | ||
imports = append(imports, node.argument.literal); | ||
} | ||
} | ||
} | ||
|
||
function collectDynamicImportOrRequireCallsForEachChild(node: Node) { | ||
forEachChild(node, collectDynamicImportOrRequireCalls); | ||
/** Returns a token if position is in [start-of-leading-trivia, end) */ | ||
function getTokenAtPosition(sourceFile: SourceFile, position: number): Node { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this differ from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it uses Node.pos and Node.end instead of the much-more-complicated Node.getStart() and Node.getFullStart() that are present in the language service. The original getTokenAtPosition handles a lot more cases as well; I'd rather wait until something else in the compiler proper needs this code to move it into utilities and harden it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. In addition it doesn't use |
||
let current: Node = sourceFile; | ||
const getContainingChild = (child: Node) => { | ||
if (child.pos <= position && (position < child.end || (position === child.end && (child.kind === SyntaxKind.EndOfFileToken)))) { | ||
return child; | ||
} | ||
}; | ||
while (true) { | ||
const child = hasJSDocNodes(current) && forEach(current.jsDoc, getContainingChild) || forEachChild(current, getContainingChild); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably unrelated to this change: IMO it should only look at JSDoc in JS files as ImportTypeNodes in JSDoc of TS files have no effect. |
||
if (!child) { | ||
return current; | ||
} | ||
current = child; | ||
} | ||
} | ||
} | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could tweak the regex to avoid false positives. though you need to adjust the loop a bit to save the result of
r.exec
to a variable./\b(?:import|require)\s*[(/<]/g
The above will not match
require.resolve
for example as it requires an opening parenthesis or a slash (typically the start of a comment) after it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we'd save much in the majority of files, and I prefer to have the simplest possible regex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this PR is about performance, I'd rather we are careful to not regress performance where possible.
There is
require.resolve
,import.meta
, in a project of mine there's a lot requiresTypeInformation and in addition these are regular verbs that could occur in a lot of comments.Each time you encounter something that contains one of these words, you need to recursively look up the node in the AST. That can potentially be very slow for large files with deep nesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the new collector is m log(n) where m is the number of
import|require
and n is the number of nodes. The old collector is n log(n), and I think m will only approach n in pathological cases.Also, the compiler is going to crash on very deep ASTs in the binder anyway. I tried to fix that by adding a flat OperatorListExpression to replace deep BinaryExpression trees, but gave up (for now) because of the amount of work needed to update all our BinaryExpression-based code.