Merge pull request #20374 from asgerf/js/typescript-5.9

JS: Support TypeScript 5.9 and support 'import defer' syntax
This commit is contained in:
Asger F
2025-09-09 20:50:04 +02:00
committed by GitHub
25 changed files with 4943 additions and 31 deletions

View File

@@ -27,7 +27,7 @@
Ruby [10]_,"up to 3.3",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``" Ruby [10]_,"up to 3.3",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``"
Rust [11]_,"Rust editions 2021 and 2024","Rust compiler","``.rs``, ``Cargo.toml``" Rust [11]_,"Rust editions 2021 and 2024","Rust compiler","``.rs``, ``Cargo.toml``"
Swift [12]_,"Swift 5.4-6.1","Swift compiler","``.swift``" Swift [12]_,"Swift 5.4-6.1","Swift compiler","``.swift``"
TypeScript [13]_,"2.6-5.8",Standard TypeScript compiler,"``.ts``, ``.tsx``, ``.mts``, ``.cts``" TypeScript [13]_,"2.6-5.9",Standard TypeScript compiler,"``.ts``, ``.tsx``, ``.mts``, ``.cts``"
.. container:: footnote-group .. container:: footnote-group

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
description: add support for deferred imports
compatibility: partial
has_defer_keyword.rel: delete

View File

@@ -6,24 +6,25 @@
"": { "": {
"name": "typescript-parser-wrapper", "name": "typescript-parser-wrapper",
"dependencies": { "dependencies": {
"typescript": "^5.8.2" "typescript": "5.9"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.15.3" "@types/node": "^24.3.1"
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.15.3", "version": "24.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
"integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
"dev": true, "dev": true,
"license": "MIT" "dependencies": {
"undici-types": "~7.10.0"
}
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.2", "version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"license": "Apache-2.0",
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -31,6 +32,12 @@
"engines": { "engines": {
"node": ">=14.17" "node": ">=14.17"
} }
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"dev": true
} }
} }
} }

View File

@@ -2,7 +2,7 @@
"name": "typescript-parser-wrapper", "name": "typescript-parser-wrapper",
"private": true, "private": true,
"dependencies": { "dependencies": {
"typescript": "^5.8.2" "typescript": "5.9"
}, },
"scripts": { "scripts": {
"build": "tsc --project tsconfig.json", "build": "tsc --project tsconfig.json",
@@ -12,6 +12,6 @@
"watch": "tsc -p . -w --sourceMap" "watch": "tsc -p . -w --sourceMap"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.15.3" "@types/node": "^24.3.1"
} }
} }

View File

@@ -267,6 +267,7 @@ const astProperties: string[] = [
"parameterName", "parameterName",
"parameters", "parameters",
"parseDiagnostics", "parseDiagnostics",
"phaseModifier",
"properties", "properties",
"propertyName", "propertyName",
"qualifier", "qualifier",

View File

@@ -61,6 +61,7 @@ import com.semmle.js.ast.IfStatement;
import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDeclaration;
import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportDefaultSpecifier;
import com.semmle.js.ast.ImportNamespaceSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier;
import com.semmle.js.ast.ImportPhaseModifier;
import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.ImportSpecifier;
import com.semmle.js.ast.LabeledStatement; import com.semmle.js.ast.LabeledStatement;
import com.semmle.js.ast.Literal; import com.semmle.js.ast.Literal;
@@ -3587,6 +3588,7 @@ public class Parser {
} }
protected ImportDeclaration parseImportRest(SourceLocation loc) { protected ImportDeclaration parseImportRest(SourceLocation loc) {
ImportPhaseModifier[] phaseModifier = { ImportPhaseModifier.NONE };
List<ImportSpecifier> specifiers; List<ImportSpecifier> specifiers;
Literal source; Literal source;
// import '...' // import '...'
@@ -3594,27 +3596,32 @@ public class Parser {
specifiers = new ArrayList<ImportSpecifier>(); specifiers = new ArrayList<ImportSpecifier>();
source = (Literal) this.parseExprAtom(null); source = (Literal) this.parseExprAtom(null);
} else { } else {
specifiers = this.parseImportSpecifiers(); specifiers = this.parseImportSpecifiers(phaseModifier);
this.expectContextual("from"); this.expectContextual("from");
if (this.type != TokenType.string) this.unexpected(); if (this.type != TokenType.string) this.unexpected();
source = (Literal) this.parseExprAtom(null); source = (Literal) this.parseExprAtom(null);
} }
Expression attributes = this.parseImportOrExportAttributesAndSemicolon(); Expression attributes = this.parseImportOrExportAttributesAndSemicolon();
if (specifiers == null) return null; if (specifiers == null) return null;
return this.finishNode(new ImportDeclaration(loc, specifiers, source, attributes)); return this.finishNode(new ImportDeclaration(loc, specifiers, source, attributes, phaseModifier[0]));
} }
// Parses a comma-separated list of module imports. // Parses a comma-separated list of module imports.
protected List<ImportSpecifier> parseImportSpecifiers() { protected List<ImportSpecifier> parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) {
List<ImportSpecifier> nodes = new ArrayList<ImportSpecifier>(); List<ImportSpecifier> nodes = new ArrayList<ImportSpecifier>();
boolean first = true; boolean first = true;
if (this.type == TokenType.name) { if (this.type == TokenType.name) {
// import defaultObj, { x, y as z } from '...' // import defaultObj, { x, y as z } from '...'
SourceLocation loc = new SourceLocation(this.startLoc); SourceLocation loc = new SourceLocation(this.startLoc);
Identifier local = this.parseIdent(false); Identifier local = this.parseIdent(false);
this.checkLVal(local, true, null); // Parse `import defer *` as the beginning of a deferred import, instead of a default import specifier
nodes.add(this.finishNode(new ImportDefaultSpecifier(loc, local))); if (this.type == TokenType.star && local.getName().equals("defer")) {
if (!this.eat(TokenType.comma)) return nodes; phaseModifier[0] = ImportPhaseModifier.DEFER;
} else {
this.checkLVal(local, true, null);
nodes.add(this.finishNode(new ImportDefaultSpecifier(loc, local)));
if (!this.eat(TokenType.comma)) return nodes;
}
} }
if (this.type == TokenType.star) { if (this.type == TokenType.star) {
SourceLocation loc = new SourceLocation(this.startLoc); SourceLocation loc = new SourceLocation(this.startLoc);
@@ -3647,7 +3654,7 @@ public class Parser {
if (this.type == TokenType.string) { if (this.type == TokenType.string) {
// Arbitrary Module Namespace Identifiers // Arbitrary Module Namespace Identifiers
// e.g. `import { "Foo::new" as Foo_new } from "./foo.wasm"` // e.g. `import { "Foo::new" as Foo_new } from "./foo.wasm"`
Expression string = this.parseExprAtom(null); Expression string = this.parseExprAtom(null);
String str = ((Literal)string).getStringValue(); String str = ((Literal)string).getStringValue();
imported = this.finishNode(new Identifier(loc, str)); imported = this.finishNode(new Identifier(loc, str));
// only makes sense if there is a local identifier // only makes sense if there is a local identifier

View File

@@ -14,6 +14,7 @@ import com.semmle.js.ast.Expression;
import com.semmle.js.ast.ExpressionStatement; import com.semmle.js.ast.ExpressionStatement;
import com.semmle.js.ast.FieldDefinition; import com.semmle.js.ast.FieldDefinition;
import com.semmle.js.ast.Identifier; import com.semmle.js.ast.Identifier;
import com.semmle.js.ast.ImportPhaseModifier;
import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.ImportSpecifier;
import com.semmle.js.ast.Literal; import com.semmle.js.ast.Literal;
import com.semmle.js.ast.MethodDefinition; import com.semmle.js.ast.MethodDefinition;
@@ -1064,13 +1065,13 @@ public class FlowParser extends ESNextParser {
} }
@Override @Override
protected List<ImportSpecifier> parseImportSpecifiers() { protected List<ImportSpecifier> parseImportSpecifiers(ImportPhaseModifier[] phaseModifier) {
String kind = null; String kind = null;
if (flow()) { if (flow()) {
kind = flowParseImportSpecifiers(); kind = flowParseImportSpecifiers();
} }
List<ImportSpecifier> specs = super.parseImportSpecifiers(); List<ImportSpecifier> specs = super.parseImportSpecifiers(phaseModifier);
if (kind != null || specs.isEmpty()) return null; if (kind != null || specs.isEmpty()) return null;
return specs; return specs;
} }

View File

@@ -26,11 +26,11 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
private int symbol = -1; private int symbol = -1;
private boolean hasTypeKeyword; private ImportPhaseModifier phaseModifier;
public ImportDeclaration( public ImportDeclaration(
SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression attributes) { SourceLocation loc, List<ImportSpecifier> specifiers, Literal source, Expression attributes) {
this(loc, specifiers, source, attributes, false); this(loc, specifiers, source, attributes, ImportPhaseModifier.NONE);
} }
public ImportDeclaration( public ImportDeclaration(
@@ -38,12 +38,12 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
List<ImportSpecifier> specifiers, List<ImportSpecifier> specifiers,
Literal source, Literal source,
Expression attributes, Expression attributes,
boolean hasTypeKeyword) { ImportPhaseModifier phaseModifier) {
super("ImportDeclaration", loc); super("ImportDeclaration", loc);
this.specifiers = specifiers; this.specifiers = specifiers;
this.source = source; this.source = source;
this.attributes = attributes; this.attributes = attributes;
this.hasTypeKeyword = hasTypeKeyword; this.phaseModifier = phaseModifier;
} }
public Literal getSource() { public Literal getSource() {
@@ -79,6 +79,15 @@ public class ImportDeclaration extends Statement implements INodeWithSymbol {
/** Returns true if this is an <code>import type</code> declaration. */ /** Returns true if this is an <code>import type</code> declaration. */
public boolean hasTypeKeyword() { public boolean hasTypeKeyword() {
return hasTypeKeyword; return phaseModifier == ImportPhaseModifier.TYPE;
}
/** Returns true if this is an <code>import defer</code> declaration. */
public boolean hasDeferKeyword() {
return phaseModifier == ImportPhaseModifier.DEFER;
}
public ImportPhaseModifier getPhaseModifier() {
return phaseModifier;
} }
} }

View File

@@ -0,0 +1,10 @@
package com.semmle.js.ast;
/**
* A keyword that may appear on import declarations.
*/
public enum ImportPhaseModifier {
NONE,
DEFER,
TYPE,
}

View File

@@ -564,7 +564,7 @@ public class NodeCopier implements Visitor<Void, INode> {
copy(nd.getSpecifiers()), copy(nd.getSpecifiers()),
copy(nd.getSource()), copy(nd.getSource()),
copy(nd.getAttributes()), copy(nd.getAttributes()),
nd.hasTypeKeyword()); nd.getPhaseModifier());
} }
@Override @Override

View File

@@ -1833,6 +1833,9 @@ public class ASTExtractor {
if (nd.hasTypeKeyword()) { if (nd.hasTypeKeyword()) {
trapwriter.addTuple("has_type_keyword", lbl); trapwriter.addTuple("has_type_keyword", lbl);
} }
if (nd.hasDeferKeyword()) {
trapwriter.addTuple("has_defer_keyword", lbl);
}
return lbl; return lbl;
} }

View File

@@ -52,6 +52,7 @@ import com.semmle.js.ast.IfStatement;
import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDeclaration;
import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportDefaultSpecifier;
import com.semmle.js.ast.ImportNamespaceSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier;
import com.semmle.js.ast.ImportPhaseModifier;
import com.semmle.js.ast.ImportSpecifier; import com.semmle.js.ast.ImportSpecifier;
import com.semmle.js.ast.InvokeExpression; import com.semmle.js.ast.InvokeExpression;
import com.semmle.js.ast.LabeledStatement; import com.semmle.js.ast.LabeledStatement;
@@ -1404,7 +1405,7 @@ public class TypeScriptASTConverter {
Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class); Literal src = tryConvertChild(node, "moduleSpecifier", Literal.class);
Expression attributes = convertChild(node, "attributes"); Expression attributes = convertChild(node, "attributes");
List<ImportSpecifier> specifiers = new ArrayList<>(); List<ImportSpecifier> specifiers = new ArrayList<>();
boolean hasTypeKeyword = false; ImportPhaseModifier phaseModifier = ImportPhaseModifier.NONE;
if (hasChild(node, "importClause")) { if (hasChild(node, "importClause")) {
JsonObject importClause = node.get("importClause").getAsJsonObject(); JsonObject importClause = node.get("importClause").getAsJsonObject();
if (hasChild(importClause, "name")) { if (hasChild(importClause, "name")) {
@@ -1418,10 +1419,22 @@ public class TypeScriptASTConverter {
specifiers.addAll(convertChildren(namedBindings, "elements")); specifiers.addAll(convertChildren(namedBindings, "elements"));
} }
} }
hasTypeKeyword = importClause.get("isTypeOnly").getAsBoolean(); if (hasChild(importClause, "phaseModifier")) {
String name = metadata.getSyntaxKindName(importClause.get("phaseModifier").getAsInt());
switch (name) {
case "DeferKeyword": {
phaseModifier = ImportPhaseModifier.DEFER;
break;
}
case "TypeKeyword": {
phaseModifier = ImportPhaseModifier.TYPE;
break;
}
}
}
} }
ImportDeclaration importDecl = ImportDeclaration importDecl =
new ImportDeclaration(loc, specifiers, src, attributes, hasTypeKeyword); new ImportDeclaration(loc, specifiers, src, attributes, phaseModifier);
attachSymbolInformation(importDecl, node); attachSymbolInformation(importDecl, node);
return importDecl; return importDecl;
} }

View File

@@ -151,6 +151,14 @@ class ImportDeclaration extends Stmt, Import, @import_declaration {
/** Holds if this is declared with the `type` keyword, so it only imports types. */ /** Holds if this is declared with the `type` keyword, so it only imports types. */
predicate isTypeOnly() { has_type_keyword(this) } predicate isTypeOnly() { has_type_keyword(this) }
/**
* Holds if this is declared with the `defer` keyword, for example:
* ```ts
* import defer * as f from "somewhere";
* ```
*/
predicate isDeferredImport() { has_defer_keyword(this) }
override string getAPrimaryQlClass() { result = "ImportDeclaration" } override string getAPrimaryQlClass() { result = "ImportDeclaration" }
} }

View File

@@ -516,6 +516,7 @@ has_private_keyword (int id: @property ref);
has_protected_keyword (int id: @property ref); has_protected_keyword (int id: @property ref);
has_readonly_keyword (int id: @property ref); has_readonly_keyword (int id: @property ref);
has_type_keyword (int id: @type_keyword_operand ref); has_type_keyword (int id: @type_keyword_operand ref);
has_defer_keyword (int id: @import_declaration ref);
is_optional_member (int id: @property ref); is_optional_member (int id: @property ref);
has_definite_assignment_assertion (int id: @field_or_vardeclarator ref); has_definite_assignment_assertion (int id: @field_or_vardeclarator ref);
is_optional_parameter_declaration (unique int parameter: @pattern ref); is_optional_parameter_declaration (unique int parameter: @pattern ref);

View File

@@ -4319,6 +4319,17 @@
<dependencies/> <dependencies/>
</relation> </relation>
<relation> <relation>
<name>has_defer_keyword</name>
<cardinality>1000</cardinality>
<columnsizes>
<e>
<k>id</k>
<v>1000</v>
</e>
</columnsizes>
<dependencies/>
</relation>
<relation>
<name>is_optional_member</name> <name>is_optional_member</name>
<cardinality>3668</cardinality> <cardinality>3668</cardinality>
<columnsizes> <columnsizes>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
description: add support for deferred imports
compatibility: backwards

View File

@@ -0,0 +1,5 @@
---
category: majorAnalysis
---
* Added support for TypeScript 5.9
* Added support for `import defer` syntax in JavaScript and TypeScript.

View File

@@ -0,0 +1,3 @@
import defer * as deferred from "somewhere";
import * as normal from "somewhere";
import defer from "somewhere";

View File

@@ -0,0 +1,2 @@
| test-js.js:1:1:1:44 | import ... where"; |
| tst.ts:1:1:1:44 | import ... where"; |

View File

@@ -0,0 +1,3 @@
import javascript
query predicate deferredImports(ImportDeclaration decl) { decl.isDeferredImport() }

View File

@@ -0,0 +1,4 @@
import defer * as deferred from "somewhere";
import type * as t from "somewhere";
import * as normal from "somewhere";
import defer from "somewhere";