From 74af9067a12e9892283ee773b3e9b2e80d954f96 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 18 May 2022 10:13:26 +0200 Subject: [PATCH 01/11] JS: Add node_modules to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fd9e5b6a07ef..165d549cee28 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ go/tools/win64 go/tools/tokenizer.jar go/main +# node_modules folders except in the JS test suite +node_modules/ +!/javascript/ql/test/**/node_modules/ From 2e858db848cc09bb87c4ee3f89198d4691ca38b0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 19 May 2022 15:05:23 +0200 Subject: [PATCH 02/11] JS: Declare variables from ambient declarations fixup --- .../semmle/js/extractor/HTMLExtractor.java | 5 +-- .../com/semmle/js/extractor/ScopeManager.java | 36 +++++++++++++------ .../semmle/js/extractor/ScriptExtractor.java | 2 +- .../js/extractor/TypeScriptExtractor.java | 6 ++-- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java index 664d898d5e1f..a03d03bfe0c1 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/HTMLExtractor.java @@ -40,7 +40,8 @@ public JavaScriptHTMLElementHandler(TextualExtractor textualExtractor) { this.textualExtractor = textualExtractor; this.scopeManager = - new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), true); + new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), + ScopeManager.FileKind.TEMPLATE); } /* @@ -425,7 +426,7 @@ private void extractTemplateTags( extractSnippet( TopLevelKind.ANGULAR_STYLE_TEMPLATE, config.withSourceType(SourceType.ANGULAR_STYLE_TEMPLATE), - new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, true), + new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2020, ScopeManager.FileKind.TEMPLATE), textualExtractor, m.group(bodyGroup), m.start(bodyGroup), diff --git a/javascript/extractor/src/com/semmle/js/extractor/ScopeManager.java b/javascript/extractor/src/com/semmle/js/extractor/ScopeManager.java index 81bdc9e6f92c..e7306e77e016 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ScopeManager.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ScopeManager.java @@ -97,20 +97,31 @@ public Label lookupNamespace(String name) { } } + public static enum FileKind { + /** Any file not specific to one of the other file kinds. */ + PLAIN, + + /** A file potentially containing template tags. */ + TEMPLATE, + + /** A d.ts file, containing TypeScript ambient declarations. */ + TYPESCRIPT_DECLARATION, + } + private final TrapWriter trapWriter; private Scope curScope; private final Scope toplevelScope; private final ECMAVersion ecmaVersion; private final Set implicitGlobals = new LinkedHashSet(); private Scope implicitVariableScope; - private final boolean isInTemplateScope; + private final FileKind fileKind; - public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, boolean isInTemplateScope) { + public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, FileKind fileKind) { this.trapWriter = trapWriter; this.toplevelScope = enterScope(ScopeKind.GLOBAL, trapWriter.globalID("global_scope"), null); this.ecmaVersion = ecmaVersion; - this.implicitVariableScope = toplevelScope; - this.isInTemplateScope = isInTemplateScope; + this.implicitVariableScope = toplevelScope; + this.fileKind = fileKind; } /** @@ -118,7 +129,11 @@ public ScopeManager(TrapWriter trapWriter, ECMAVersion ecmaVersion, boolean isIn * relevant template tags. */ public boolean isInTemplateFile() { - return isInTemplateScope; + return this.fileKind == FileKind.TEMPLATE; + } + + public boolean isInTypeScriptDeclarationFile() { + return this.fileKind == FileKind.TYPESCRIPT_DECLARATION; } /** @@ -221,7 +236,7 @@ public Scope getToplevelScope() { /** * Get the label for a given variable in the current scope; if it cannot be found, add it to the - * implicit variable scope (usually the global scope). + * implicit variable scope (usually the global scope). */ public Label getVarKey(String name) { Label lbl = curScope.lookupVariable(name); @@ -411,7 +426,7 @@ public Void visit(Identifier nd, Void v) { // cases where we turn on the 'declKind' flags @Override public Void visit(FunctionDeclaration nd, Void v) { - if (nd.hasDeclareKeyword()) return null; + if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null; // strict mode functions are block-scoped, non-strict mode ones aren't if (blockscope == isStrict) visit(nd.getId(), DeclKind.var); return null; @@ -419,7 +434,7 @@ public Void visit(FunctionDeclaration nd, Void v) { @Override public Void visit(ClassDeclaration nd, Void c) { - if (nd.hasDeclareKeyword()) return null; + if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null; if (blockscope) visit(nd.getClassDef().getId(), DeclKind.varAndType); return null; } @@ -468,7 +483,7 @@ public Void visit(EnhancedForStatement nd, Void v) { @Override public Void visit(VariableDeclaration nd, Void v) { - if (nd.hasDeclareKeyword()) return null; + if (nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile()) return null; // in block scoping mode, only process 'let'; in non-block scoping // mode, only process non-'let' if (blockscope == nd.isBlockScoped(ecmaVersion)) visit(nd.getDeclarations()); @@ -503,7 +518,8 @@ public Void visit(ClassBody nd, Void c) { @Override public Void visit(NamespaceDeclaration nd, Void c) { if (blockscope) return null; - boolean hasVariable = nd.isInstantiated() && !nd.hasDeclareKeyword(); + boolean isAmbientOutsideDtsFile = nd.hasDeclareKeyword() && !isInTypeScriptDeclarationFile(); + boolean hasVariable = nd.isInstantiated() && !isAmbientOutsideDtsFile; visit(nd.getName(), hasVariable ? DeclKind.varAndNamespace : DeclKind.namespace); return null; } diff --git a/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java index 08d29d98a9b2..b4b47a786f90 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ScriptExtractor.java @@ -77,7 +77,7 @@ public LoCInfo extract(TextualExtractor textualExtractor) { } ScopeManager scopeManager = - new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), false); + new ScopeManager(textualExtractor.getTrapwriter(), config.getEcmaVersion(), ScopeManager.FileKind.PLAIN); Label toplevelLabel = null; LoCInfo loc; try { diff --git a/javascript/extractor/src/com/semmle/js/extractor/TypeScriptExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/TypeScriptExtractor.java index 629abccb4227..623c6ec7fc81 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/TypeScriptExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/TypeScriptExtractor.java @@ -22,8 +22,10 @@ public LoCInfo extract(TextualExtractor textualExtractor) { String source = textualExtractor.getSource(); File sourceFile = textualExtractor.getExtractedFile(); Result res = state.getTypeScriptParser().parse(sourceFile, source, textualExtractor.getMetrics()); - ScopeManager scopeManager = - new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, false); + ScopeManager.FileKind fileKind = sourceFile.getName().endsWith(".d.ts") + ? ScopeManager.FileKind.TYPESCRIPT_DECLARATION + : ScopeManager.FileKind.PLAIN; + ScopeManager scopeManager = new ScopeManager(textualExtractor.getTrapwriter(), ECMAVersion.ECMA2017, fileKind); try { FileSnippet snippet = state.getSnippets().get(sourceFile.toPath()); SourceType sourceType = snippet != null ? snippet.getSourceType() : jsExtractor.establishSourceType(source, false); From 987a83002986585ac70eaf6b211d4815e414a8b0 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 23 May 2022 11:14:59 +0200 Subject: [PATCH 03/11] JS: Add test for import of d.ts file --- .../RegressionTests/ImportDtsFile/has-javascript-file.d.ts | 3 +++ .../RegressionTests/ImportDtsFile/has-javascript-file.js | 1 + .../TypeScript/RegressionTests/ImportDtsFile/main.ts | 5 +++++ .../RegressionTests/ImportDtsFile/only-declaration-file.d.ts | 3 +++ .../TypeScript/RegressionTests/ImportDtsFile/test.expected | 1 + .../TypeScript/RegressionTests/ImportDtsFile/test.ql | 4 ++++ 6 files changed, 17 insertions(+) create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.d.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.js create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/main.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/only-declaration-file.d.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.ql diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.d.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.d.ts new file mode 100644 index 000000000000..d1482351cd7a --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.d.ts @@ -0,0 +1,3 @@ +// This file has a corresponding .js file, which will be preferred by import resolution. + +export const x: number; diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.js b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.js new file mode 100644 index 000000000000..310d1059fd1c --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/has-javascript-file.js @@ -0,0 +1 @@ +export const x = 42; diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/main.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/main.ts new file mode 100644 index 000000000000..578233fbe3a3 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/main.ts @@ -0,0 +1,5 @@ +import * as declFile from "./only-declaration-file"; +import * as jsFile from "./has-javascript-file"; + +console.log(declFile.x); +console.log(jsFile.x); diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/only-declaration-file.d.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/only-declaration-file.d.ts new file mode 100644 index 000000000000..32ff2c67e6aa --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/only-declaration-file.d.ts @@ -0,0 +1,3 @@ +// This file has no corresponding implementation, so it should be seen by import resolution. + +export const x: number; diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected new file mode 100644 index 000000000000..728627a40b85 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected @@ -0,0 +1 @@ +| main.ts:2:1:2:48 | import ... -file"; | ./has-javascript-file | has-javascript-file.js:1:1:2:0 | | diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.ql new file mode 100644 index 000000000000..3c5e8a7fd168 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.ql @@ -0,0 +1,4 @@ +import javascript + +from Import imprt +select imprt, imprt.getImportedPath().getValue(), imprt.getImportedModule() From c8bb0e211743613308205281e00e7a02a1284ac9 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 20 May 2022 20:06:54 +0200 Subject: [PATCH 04/11] JS: Treat d.ts as a single extension in Folder.getJavaScriptFile --- javascript/ql/lib/semmle/javascript/Files.qll | 15 ++++++++++++++- .../javascript/NodeModuleResolutionImpl.qll | 2 ++ .../RegressionTests/ImportDtsFile/test.expected | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/javascript/ql/lib/semmle/javascript/Files.qll b/javascript/ql/lib/semmle/javascript/Files.qll index 92a20c37d67a..c01810b462f3 100644 --- a/javascript/ql/lib/semmle/javascript/Files.qll +++ b/javascript/ql/lib/semmle/javascript/Files.qll @@ -175,6 +175,15 @@ class Folder extends Container, @folder { result.getExtension() = extension } + /** Like `getFile` except `d.ts` is treated as a single extension. */ + private File getFileLongExtension(string stem, string extension) { + not (stem.matches("%.d") and extension = "ts") and + result = this.getFile(stem, extension) + or + extension = "d.ts" and + result = this.getFile(stem + ".d", "ts") + } + /** * Gets the file in this folder that has the given `stem` and any of the supported JavaScript extensions. * @@ -188,7 +197,11 @@ class Folder extends Container, @folder { */ File getJavaScriptFile(string stem) { result = - min(int p, string ext | p = getFileExtensionPriority(ext) | this.getFile(stem, ext) order by p) + min(int p, string ext | + p = getFileExtensionPriority(ext) + | + this.getFileLongExtension(stem, ext) order by p + ) } /** Gets a subfolder contained in this folder. */ diff --git a/javascript/ql/lib/semmle/javascript/NodeModuleResolutionImpl.qll b/javascript/ql/lib/semmle/javascript/NodeModuleResolutionImpl.qll index 26aa1163e7e6..70f27ccb12d8 100644 --- a/javascript/ql/lib/semmle/javascript/NodeModuleResolutionImpl.qll +++ b/javascript/ql/lib/semmle/javascript/NodeModuleResolutionImpl.qll @@ -33,6 +33,8 @@ int getFileExtensionPriority(string ext) { ext = "json" and result = 8 or ext = "node" and result = 9 + or + ext = "d.ts" and result = 10 } int prioritiesPerCandidate() { result = 3 * (numberOfExtensions() + 1) } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected index 728627a40b85..fdbbf878689a 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportDtsFile/test.expected @@ -1 +1,2 @@ +| main.ts:1:1:1:52 | import ... -file"; | ./only-declaration-file | only-declaration-file.d.ts:3:1:4:0 | | | main.ts:2:1:2:48 | import ... -file"; | ./has-javascript-file | has-javascript-file.js:1:1:2:0 | | From 665fa2af59491759f7512ffb01455afa18c7df2b Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 23 May 2022 11:02:37 +0200 Subject: [PATCH 05/11] JS: Add test for export base scope --- .../RegressionTests/ExportBaseResolution/test.d.ts | 11 +++++++++++ .../ExportBaseResolution/test.expected | 1 + .../RegressionTests/ExportBaseResolution/test.ql | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.d.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.ql diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.d.ts b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.d.ts new file mode 100644 index 000000000000..219efa46d12e --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.d.ts @@ -0,0 +1,11 @@ +declare namespace A.B { + namespace C { + interface I { } + } + declare var C: number; + declare interface C { } +} + +declare module 'test' { + export = A.B.C; +} diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected new file mode 100644 index 000000000000..3825d9e676ad --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected @@ -0,0 +1 @@ +| A | variable | test.d.ts:1:19:1:19 | A | test.d.ts:10:14:10:14 | A | diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.ql new file mode 100644 index 000000000000..9d1a4dd57d0b --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.ql @@ -0,0 +1,5 @@ +import javascript + +from LexicalName name, LexicalDecl decl, LexicalAccess access +where decl.getALexicalName() = name and access.getALexicalName() = name +select name.getName(), name.getDeclarationSpace(), decl, access From d7e3e9e5db90ad45967a1cc209e241d00168da97 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 20 May 2022 22:04:11 +0200 Subject: [PATCH 06/11] JS: Fix extraction of identifiers in EXPORT_BASE context This is needed to ensure that the base of the RHS of an ImportEqualsDeclaration is bound to a namespace. That is, B below should be bound to a namespace: import A = B.C.D; --- .../extractor/src/com/semmle/js/extractor/ASTExtractor.java | 2 +- .../RegressionTests/ExportBaseResolution/test.expected | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index e1c059a90884..34e6dda3c64d 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -747,7 +747,7 @@ public Label visit(MemberExpression nd, Context c) { visit(nd.getProperty(), key, 1, IdContext.TYPE_LABEL); } else { IdContext baseIdContext = - c.idcontext == IdContext.EXPORT ? IdContext.EXPORT_BASE : IdContext.VAR_BIND; + (c.idcontext == IdContext.EXPORT || c.idcontext == IdContext.EXPORT_BASE) ? IdContext.EXPORT_BASE : IdContext.VAR_BIND; visit(nd.getObject(), key, 0, baseIdContext); visit(nd.getProperty(), key, 1, nd.isComputed() ? IdContext.VAR_BIND : IdContext.LABEL); } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected index 3825d9e676ad..9104b6e76c36 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ExportBaseResolution/test.expected @@ -1 +1,2 @@ +| A | namespace | test.d.ts:1:19:1:19 | A | test.d.ts:10:14:10:14 | A | | A | variable | test.d.ts:1:19:1:19 | A | test.d.ts:10:14:10:14 | A | From ec55c84abfdf03933e61dfef01e045c5a023df76 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 20 May 2022 22:04:25 +0200 Subject: [PATCH 07/11] JS: Whitespace fixes in ASTExtractor --- .../src/com/semmle/js/extractor/ASTExtractor.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index 34e6dda3c64d..859977395c01 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -590,7 +590,7 @@ public Label visit(Literal nd, Context c) { trapwriter.addTuple("literals", valueString, source, key); Position start = nd.getLoc().getStart(); com.semmle.util.locations.Position startPos = new com.semmle.util.locations.Position(start.getLine(), start.getColumn() + 1 /* Convert from 0-based to 1-based. */, start.getOffset()); - + if (nd.isRegExp()) { OffsetTranslation offsets = new OffsetTranslation(); offsets.set(0, 1); // skip the initial '/' @@ -622,7 +622,7 @@ private boolean isOctalDigit(char ch) { /** * Constant-folds simple string concatenations in `exp` while keeping an offset translation * that tracks back to the original source. - */ + */ private Pair getStringConcatResult(Expression exp) { if (exp instanceof BinaryExpression) { BinaryExpression be = (BinaryExpression) exp; @@ -636,7 +636,7 @@ private Pair getStringConcatResult(Expression exp) { if (str.length() > 1000) { return null; } - + int delta = be.getRight().getLoc().getStart().getOffset() - be.getLeft().getLoc().getStart().getOffset(); int offset = left.fst().length(); return Pair.make(str, left.snd().append(right.snd(), offset, delta)); @@ -848,14 +848,14 @@ public Label visit(AssignmentExpression nd, Context c) { public Label visit(BinaryExpression nd, Context c) { Label key = super.visit(nd, c); if (nd.getOperator().equals("in") && nd.getLeft() instanceof Identifier && ((Identifier)nd.getLeft()).getName().startsWith("#")) { - // this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in). + // this happens with Ergonomic brand checks for Private Fields (see https://github.com/tc39/proposal-private-fields-in-in). // it's the only case where private field identifiers are used not as a field. visit(nd.getLeft(), key, 0, IdContext.LABEL, true); } else { visit(nd.getLeft(), key, 0, true); } visit(nd.getRight(), key, 1, true); - + extractRegxpFromBinop(nd, c); return key; } @@ -1815,7 +1815,7 @@ public Label visit(ImportSpecifier nd, Context c) { visit(nd.getLocal(), lbl, 1, nd.hasTypeKeyword() ? IdContext.TYPE_ONLY_IMPORT : c.idcontext); if (nd.hasTypeKeyword()) { trapwriter.addTuple("has_type_keyword", lbl); - } + } return lbl; } From a5f2c949d3c14d44a9c4cc1dacddddb739bad926 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 22 Apr 2022 10:17:41 +0200 Subject: [PATCH 08/11] JS: Add UnionOrIntersectionTypeExpr --- .../ql/lib/semmle/javascript/TypeScript.qll | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/javascript/ql/lib/semmle/javascript/TypeScript.qll b/javascript/ql/lib/semmle/javascript/TypeScript.qll index 70a1ed98830f..e8546e7a9db9 100644 --- a/javascript/ql/lib/semmle/javascript/TypeScript.qll +++ b/javascript/ql/lib/semmle/javascript/TypeScript.qll @@ -896,21 +896,28 @@ class ArrayTypeExpr extends @array_typeexpr, TypeExpr { override string getAPrimaryQlClass() { result = "ArrayTypeExpr" } } +private class RawUnionOrIntersectionTypeExpr = @union_typeexpr or @intersection_typeexpr; + /** - * A union type, such as `string|number|boolean`. + * A union or intersection type, such as `string|number|boolean` or `A & B`. */ -class UnionTypeExpr extends @union_typeexpr, TypeExpr { - /** Gets the `n`th type in the union, starting at 0. */ +class UnionOrIntersectionTypeExpr extends RawUnionOrIntersectionTypeExpr, TypeExpr { + /** Gets the `n`th type in the union or intersection, starting at 0. */ TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) } - /** Gets any of the types in the union. */ + /** Gets any of the types in the union or intersection. */ TypeExpr getAnElementType() { result = this.getElementType(_) } - /** Gets the number of types in the union. This is always at least two. */ + /** Gets the number of types in the union or intersection. This is always at least two. */ int getNumElementType() { result = count(this.getAnElementType()) } override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() } +} +/** + * A union type, such as `string|number|boolean`. + */ +class UnionTypeExpr extends @union_typeexpr, UnionOrIntersectionTypeExpr { override string getAPrimaryQlClass() { result = "UnionTypeExpr" } } @@ -932,18 +939,7 @@ class IndexedAccessTypeExpr extends @indexed_access_typeexpr, TypeExpr { * * In general, there are can more than two operands to an intersection type. */ -class IntersectionTypeExpr extends @intersection_typeexpr, TypeExpr { - /** Gets the `n`th operand of the intersection type, starting at 0. */ - TypeExpr getElementType(int n) { result = this.getChildTypeExpr(n) } - - /** Gets any of the operands to the intersection type. */ - TypeExpr getAnElementType() { result = this.getElementType(_) } - - /** Gets the number of operands to the intersection type. This is always at least two. */ - int getNumElementType() { result = count(this.getAnElementType()) } - - override TypeExpr getAnUnderlyingType() { result = this.getAnElementType().getAnUnderlyingType() } - +class IntersectionTypeExpr extends @intersection_typeexpr, UnionOrIntersectionTypeExpr { override string getAPrimaryQlClass() { result = "IntersectionTypeExpr" } } From 039a7ba828655cedfd34e1045b32430a1cbca450 Mon Sep 17 00:00:00 2001 From: Asger F Date: Tue, 17 May 2022 17:33:36 +0200 Subject: [PATCH 09/11] JS: Handle .d.mts files when generating module bindings --- javascript/extractor/lib/typescript/src/main.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index eb3ce99a875b..626c8a593b0e 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -670,6 +670,12 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { if (file.endsWith(".d.ts")) { return pathlib.basename(file, ".d.ts"); } + if (file.endsWith(".d.mts") || file.endsWith(".d.cts")) { + // We don't extract d.mts or d.cts files, but their symbol can coincide with that of a d.ts file, + // which means any module bindings we generate for it will ultimately be visible in QL. + let base = pathlib.basename(file); + return base.substring(0, base.length - '.d.mts'.length); + } let base = pathlib.basename(file); let dot = base.lastIndexOf('.'); return dot === -1 || dot === 0 ? base : base.substring(0, dot); From ced1d21405e1b433490b12e3c4bb25fdb4bc14b3 Mon Sep 17 00:00:00 2001 From: Asger F Date: Mon, 23 May 2022 12:42:21 +0200 Subject: [PATCH 10/11] JS: Add getters for DeclarationSpace members --- javascript/ql/lib/semmle/javascript/Variables.qll | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/javascript/ql/lib/semmle/javascript/Variables.qll b/javascript/ql/lib/semmle/javascript/Variables.qll index ce2ab2140a94..00b463f8a9bb 100644 --- a/javascript/ql/lib/semmle/javascript/Variables.qll +++ b/javascript/ql/lib/semmle/javascript/Variables.qll @@ -873,6 +873,18 @@ class DeclarationSpace extends string { DeclarationSpace() { this = "variable" or this = "type" or this = "namespace" } } +/** Module containing the `DeclarationSpace` constants. */ +module DeclarationSpace { + /** Gets the declaration space for variables/values. */ + DeclarationSpace variable() { result = "variable" } + + /** Gets the declaration space for types. */ + DeclarationSpace type() { result = "type" } + + /** Gets the declaration space for namespaces. */ + DeclarationSpace namespace() { result = "namespace" } +} + /** * A name that is declared in a particular scope. * From a60caced98be70336eaf6120fe06091037a32232 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 25 May 2022 15:59:58 +0200 Subject: [PATCH 11/11] JS: Update TRAP output --- .../output/trap/exportasnamespace.d.ts.trap | 42 +++++++++---------- .../tests/ts/output/trap/namespaces.ts.trap | 3 +- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap b/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap index 441258563a34..2dcb1426943c 100644 --- a/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap +++ b/javascript/extractor/tests/ts/output/trap/exportasnamespace.d.ts.trap @@ -97,28 +97,28 @@ scopenodes(#20001,#20033) scopenesting(#20033,#20000) is_module(#20001) is_es2015_module(#20001) -#20034=* -stmts(#20034,30,#20001,0,"export ... foo();") -hasLocation(#20034,#20003) -stmt_containers(#20034,#20001) +#20034=@"var;{foo};{#20033}" +variables(#20034,"foo",#20033) #20035=* -stmts(#20035,17,#20034,-1,"declare ... foo();") -#20036=@"loc,{#10000},1,8,1,30" -locations_default(#20036,#10000,1,8,1,30) -hasLocation(#20035,#20036) +stmts(#20035,30,#20001,0,"export ... foo();") +hasLocation(#20035,#20003) stmt_containers(#20035,#20001) -has_declare_keyword(#20035) -#20037=* -exprs(#20037,78,#20035,-1,"foo") -hasLocation(#20037,#20013) -expr_containers(#20037,#20035) -literals("foo","foo",#20037) -#20038=@"var;{foo};{#20000}" -variables(#20038,"foo",#20000) -decl(#20037,#20038) +#20036=* +stmts(#20036,17,#20035,-1,"declare ... foo();") +#20037=@"loc,{#10000},1,8,1,30" +locations_default(#20037,#10000,1,8,1,30) +hasLocation(#20036,#20037) +stmt_containers(#20036,#20001) +has_declare_keyword(#20036) +#20038=* +exprs(#20038,78,#20036,-1,"foo") +hasLocation(#20038,#20013) +expr_containers(#20038,#20036) +literals("foo","foo",#20038) +decl(#20038,#20034) #20039=* scopes(#20039,1) -scopenodes(#20035,#20039) +scopenodes(#20036,#20039) scopenesting(#20039,#20033) #20040=@"var;{arguments};{#20039}" variables(#20040,"arguments",#20039) @@ -142,8 +142,8 @@ hasLocation(#20043,#20044) exit_cfg_node(#20045,#20001) hasLocation(#20045,#20031) successor(#20041,#20045) -successor(#20034,#20035) -successor(#20035,#20041) -successor(#20043,#20034) +successor(#20035,#20036) +successor(#20036,#20041) +successor(#20043,#20035) numlines(#10000,2,2,0) filetype(#10000,"typescript") diff --git a/javascript/extractor/tests/ts/output/trap/namespaces.ts.trap b/javascript/extractor/tests/ts/output/trap/namespaces.ts.trap index fa7d897c6ffc..5d9345a82be4 100644 --- a/javascript/extractor/tests/ts/output/trap/namespaces.ts.trap +++ b/javascript/extractor/tests/ts/output/trap/namespaces.ts.trap @@ -305,11 +305,12 @@ hasLocation(#20096,#20097) enclosing_stmt(#20096,#20092) expr_containers(#20096,#20001) #20098=* -exprs(#20098,79,#20096,0,"M") +exprs(#20098,103,#20096,0,"M") hasLocation(#20098,#20052) enclosing_stmt(#20098,#20092) expr_containers(#20098,#20001) literals("M","M",#20098) +namespacebind(#20098,#20069) bind(#20098,#20066) #20099=* exprs(#20099,0,#20096,1,"N")