Merge remote-tracking branch 'upstream/master' into deferredModel

This commit is contained in:
Erik Krogh Kristensen
2019-11-05 15:08:47 +01:00
911 changed files with 38307 additions and 11309 deletions

View File

@@ -1,13 +0,0 @@
+ semmlecode-javascript-queries/Declarations/BuiltinRedefined.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/InefficientMethodDefinition.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Expressions/BadParityCheck.ql: /Correctness/Expressions
+ semmlecode-javascript-queries/Expressions/HapaxLegomenon.ql: /Correctness/Expressions
+ semmlecode-javascript-queries/JSDoc/UnknownTagType.ql: /Readability/JSDoc
+ semmlecode-javascript-queries/JSLint/InvalidJSLintDirective.ql: /Frameworks/JSLint
+ semmlecode-javascript-queries/JSLint/MalformedJSLintDirective.ql: /Frameworks/JSLint
+ semmlecode-javascript-queries/LanguageFeatures/HTMLComments.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/MultilineStringLiteral.ql: /Readability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/ReservedWords.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/TrailingComma.ql: /Correctness/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/OctalLiteral.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/StandardLibrary/ParseIntRadix.ql: /Maintainability/Standard Library

View File

@@ -7,6 +7,7 @@
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedProperty.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Declarations/UnreachableMethodOverloads.ql: /Maintainability/Declarations
+ semmlecode-javascript-queries/Expressions/UnneededDefensiveProgramming.ql: /Maintainability/Expressions
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features
+ semmlecode-javascript-queries/LanguageFeatures/ConditionalComments.ql: /Maintainability/Language Features

View File

@@ -4,10 +4,12 @@
+ semmlecode-javascript-queries/Security/CWE-020/IncompleteUrlSubstringSanitization.ql: /Security/CWE/CWE-020
+ semmlecode-javascript-queries/Security/CWE-020/IncorrectSuffixCheck.ql: /Security/CWE/CWE-020
+ semmlecode-javascript-queries/Security/CWE-020/MissingRegExpAnchor.ql: /Security/CWE/CWE-020
+ semmlecode-javascript-queries/Security/CWE-020/UselessRegExpCharacterEscape.ql: /Security/CWE/CWE-020
+ semmlecode-javascript-queries/Security/CWE-022/TaintedPath.ql: /Security/CWE/CWE-022
+ semmlecode-javascript-queries/Security/CWE-022/ZipSlip.ql: /Security/CWE/CWE-022
+ semmlecode-javascript-queries/Security/CWE-078/CommandInjection.ql: /Security/CWE/CWE-078
+ semmlecode-javascript-queries/Security/CWE-078/IndirectCommandInjection.ql: /Security/CWE/CWE-078
+ semmlecode-javascript-queries/Security/CWE-078/ShellCommandInjectionFromEnvironment.ql: /Security/CWE/CWE-078
+ semmlecode-javascript-queries/Security/CWE-079/ReflectedXss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/StoredXss.ql: /Security/CWE/CWE-079
+ semmlecode-javascript-queries/Security/CWE-079/Xss.ql: /Security/CWE/CWE-079

View File

@@ -2,19 +2,16 @@
"name": "typescript-parser-wrapper",
"private": true,
"dependencies": {
"typescript": "3.5.1"
"typescript": "^3.7.2"
},
"scripts": {
"build": "tsc --project tsconfig.json && rollup -c",
"build": "tsc --project tsconfig.json",
"check": "tsc --noEmit --project . && tslint --project .",
"lint": "tslint --project .",
"lint-fix": "tslint --project . --fix"
},
"devDependencies": {
"@types/node": "^9.3.0",
"rollup": "^0.66.6",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-resolve": "^3.4.0",
"@types/node": "12.7.11",
"tslint": "^5.9.1"
}
}

View File

@@ -1,51 +0,0 @@
import resolve from "rollup-plugin-node-resolve";
import commonjs from "rollup-plugin-commonjs";
import * as fs from "fs";
import * as pathlib from "path";
const copyTypeScriptFiles = (options) => ({
generateBundle() {
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
}
let typescriptDir = pathlib.dirname(require.resolve("typescript"));
for (let file of fs.readdirSync(typescriptDir)) {
// Don't include bundles like `tsc.js`.
if (file.endsWith(".js")) continue;
// Only include library typings, not `typescript.d.ts` and friends
if (file.endsWith(".d.ts") && !file.startsWith("lib")) continue;
let filePath = `${typescriptDir}/${file}`;
// Skip directories. They contain locale translations and are not needed.
if (fs.statSync(filePath).isDirectory()) continue;
fs.copyFileSync(filePath, `dist/${file}`);
}
}
});
export default {
input: 'build/main.js',
output: {
file: 'dist/main.js',
format: 'cjs'
},
plugins: [
resolve(), // Resolve paths using Node.js rules
commonjs({ // Make rollup understand `require`
ignore: [ 'source-map-support' ] // Optional required - do not hoist to top-level.
}),
copyTypeScriptFiles(), // Copy files needed by TypeScript compiler
],
// List Node.js modules to avoid warnings about unresolved modules.
external: [
'buffer',
'crypto',
'fs',
'os',
'path',
'readline',
],
}

View File

@@ -126,7 +126,7 @@ function checkCycle(root: any) {
function isBlacklistedProperty(k: string) {
return k === "parent" || k === "pos" || k === "end"
|| k === "symbol" || k === "localSymbol"
|| k === "flowNode" || k === "returnFlowNode"
|| k === "flowNode" || k === "returnFlowNode" || k === "endFlowNode"
|| k === "nextContainer" || k === "locals"
|| k === "bindDiagnostics" || k === "bindSuggestionDiagnostics";
}
@@ -162,10 +162,10 @@ function prepareNextFile() {
}
}
function handleParseCommand(command: ParseCommand) {
function handleParseCommand(command: ParseCommand, checkPending = true) {
let filename = command.filename;
let expectedFilename = state.pendingFiles[state.pendingFileIndex];
if (expectedFilename !== filename) {
if (expectedFilename !== filename && checkPending) {
throw new Error("File requested out of order. Expected '" + expectedFilename + "' but got '" + filename + "'");
}
++state.pendingFileIndex;
@@ -515,13 +515,13 @@ if (process.argv.length > 2) {
handleParseCommand({
command: "parse",
filename: sf.fileName,
});
}, false);
}
} else if (pathlib.extname(argument) === ".ts" || pathlib.extname(argument) === ".tsx") {
handleParseCommand({
command: "parse",
filename: argument,
});
}, false);
} else {
console.error("Unrecognized file or flag: " + argument);
}

View File

@@ -597,7 +597,7 @@ export class TypeTable {
let tupleType = tupleReference.target;
let minLength = tupleType.minLength != null
? tupleType.minLength
: tupleReference.typeArguments.length;
: this.typeChecker.getTypeArguments(tupleReference).length;
let hasRestElement = tupleType.hasRestElement ? 't' : 'f';
let prefix = `tuple;${minLength};${hasRestElement}`;
return this.makeTypeStringVectorFromTypeReferenceArguments(prefix, type);
@@ -714,11 +714,12 @@ export class TypeTable {
// There can be an extra type argument at the end, denoting an explicit 'this' type argument.
// We discard the extra argument in our model.
let target = type.target;
if (type.typeArguments == null) return tag;
let typeArguments = this.typeChecker.getTypeArguments(type);
if (typeArguments == null) return tag;
if (target.typeParameters != null) {
return this.makeTypeStringVector(tag, type.typeArguments, target.typeParameters.length);
return this.makeTypeStringVector(tag, typeArguments, target.typeParameters.length);
} else {
return this.makeTypeStringVector(tag, type.typeArguments);
return this.makeTypeStringVector(tag, typeArguments);
}
}
@@ -992,7 +993,7 @@ export class TypeTable {
* `T` is the type parameter declared on the `Promise` interface.
*/
private getSelfType(type: ts.Type): ts.TypeReference {
if (isTypeReference(type) && type.typeArguments != null && type.typeArguments.length > 0) {
if (isTypeReference(type) && this.typeChecker.getTypeArguments(type).length > 0) {
return type.target;
}
return null;
@@ -1181,8 +1182,9 @@ export class TypeTable {
if (isTypeReference(type)) {
// Note that this case also handles tuple types, since a tuple type is represented as
// a reference to a synthetic generic interface.
if (type.typeArguments != null) {
type.typeArguments.forEach(callback);
let typeArguments = this.typeChecker.getTypeArguments(type);
if (typeArguments != null) {
typeArguments.forEach(callback);
}
} else if (type.flags & ts.TypeFlags.UnionOrIntersection) {
(type as ts.UnionOrIntersectionType).types.forEach(callback);

View File

@@ -3,6 +3,5 @@ let overridePath = process.env['SEMMLE_TYPESCRIPT_HOME'];
if (overridePath != null) {
module.exports = require(overridePath);
} else {
// Unlike the above, this require() call will be rewritten by rollup.
module.exports = require('typescript');
}

View File

@@ -2,17 +2,9 @@
# yarn lockfile v1
"@types/estree@0.0.39":
version "0.0.39"
resolved "estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
"@types/node@*":
version "10.12.0"
resolved "node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235"
"@types/node@^9.3.0":
version "9.3.0"
resolved "@types-node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5"
"@types/node@12.7.11":
version "12.7.11"
resolved "node-12.7.11.tgz#be879b52031cfb5d295b047f5462d8ef1a716446"
ansi-regex@^2.0.0:
version "2.1.1"
@@ -34,20 +26,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
arr-diff@^2.0.0:
version "2.0.0"
resolved "arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
dependencies:
arr-flatten "^1.0.1"
arr-flatten@^1.0.1:
version "1.1.0"
resolved "arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
array-unique@^0.2.1:
version "0.2.1"
resolved "array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
babel-code-frame@^6.22.0:
version "6.26.0"
resolved "babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -67,22 +45,10 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^1.8.2:
version "1.8.5"
resolved "ces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7"
dependencies:
expand-range "^1.8.1"
preserve "^0.2.0"
repeat-element "^1.1.2"
builtin-modules@^1.1.1:
version "1.1.1"
resolved "builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
builtin-modules@^2.0.0:
version "2.0.0"
resolved "builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
chalk@^1.1.3:
version "1.1.3"
resolved "chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -131,73 +97,14 @@ esprima@^4.0.0:
version "4.0.0"
resolved "esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
estree-walker@^0.5.2:
version "0.5.2"
resolved "estree-walker-0.5.2.tgz#d3850be7529c9580d815600b53126515e146dd39"
esutils@^2.0.2:
version "2.0.2"
resolved "esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
expand-brackets@^0.1.4:
version "0.1.5"
resolved "expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
dependencies:
is-posix-bracket "^0.1.0"
expand-range@^1.8.1:
version "1.8.2"
resolved "expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337"
dependencies:
fill-range "^2.1.0"
extglob@^0.3.1:
version "0.3.2"
resolved "glob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
dependencies:
is-extglob "^1.0.0"
filename-regex@^2.0.0:
version "2.0.1"
resolved "filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
fill-range@^2.1.0:
version "2.2.4"
resolved "fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565"
dependencies:
is-number "^2.1.0"
isobject "^2.0.0"
randomatic "^3.0.0"
repeat-element "^1.1.2"
repeat-string "^1.5.2"
for-in@^1.0.1:
version "1.0.2"
resolved "for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
for-own@^0.1.4:
version "0.1.5"
resolved "for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce"
dependencies:
for-in "^1.0.1"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
glob-base@^0.3.0:
version "0.3.0"
resolved "glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
dependencies:
glob-parent "^2.0.0"
is-glob "^2.0.0"
glob-parent@^2.0.0:
version "2.0.0"
resolved "glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28"
dependencies:
is-glob "^2.0.0"
glob@^7.1.1:
version "7.1.2"
resolved "glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
@@ -230,66 +137,6 @@ inherits@2:
version "2.0.3"
resolved "inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
is-buffer@^1.1.5:
version "1.1.6"
resolved "is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
is-dotfile@^1.0.0:
version "1.0.3"
resolved "is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1"
is-equal-shallow@^0.1.3:
version "0.1.3"
resolved "allow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534"
dependencies:
is-primitive "^2.0.0"
is-extendable@^0.1.1:
version "0.1.1"
resolved "is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
is-extglob@^1.0.0:
version "1.0.0"
resolved "is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0"
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
dependencies:
is-extglob "^1.0.0"
is-module@^1.0.0:
version "1.0.0"
resolved "is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
is-number@^2.1.0:
version "2.1.0"
resolved "is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
dependencies:
kind-of "^3.0.2"
is-number@^4.0.0:
version "4.0.0"
resolved "is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff"
is-posix-bracket@^0.1.0:
version "0.1.1"
resolved "acket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
is-primitive@^2.0.0:
version "2.0.0"
resolved "is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575"
isarray@1.0.0:
version "1.0.0"
resolved "rray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isobject@^2.0.0:
version "2.1.0"
resolved "bject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
dependencies:
isarray "1.0.0"
js-tokens@^3.0.2:
version "3.0.2"
resolved "js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@@ -301,78 +148,18 @@ js-yaml@^3.7.0:
argparse "^1.0.7"
esprima "^4.0.0"
kind-of@^3.0.2:
version "3.2.2"
resolved "kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
dependencies:
is-buffer "^1.1.5"
kind-of@^6.0.0:
version "6.0.2"
resolved "kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
magic-string@^0.25.1:
version "0.25.1"
resolved "magic-string-0.25.1.tgz#b1c248b399cd7485da0fe7385c2fc7011843266e"
dependencies:
sourcemap-codec "^1.4.1"
math-random@^1.0.1:
version "1.0.1"
resolved "math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
micromatch@^2.3.11:
version "2.3.11"
resolved "romatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
dependencies:
arr-diff "^2.0.0"
array-unique "^0.2.1"
braces "^1.8.2"
expand-brackets "^0.1.4"
extglob "^0.3.1"
filename-regex "^2.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.1"
kind-of "^3.0.2"
normalize-path "^2.0.1"
object.omit "^2.0.0"
parse-glob "^3.0.4"
regex-cache "^0.4.2"
minimatch@^3.0.4:
version "3.0.4"
resolved "minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
brace-expansion "^1.1.7"
normalize-path@^2.0.1:
version "2.1.1"
resolved "normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
dependencies:
remove-trailing-separator "^1.0.1"
object.omit@^2.0.0:
version "2.0.1"
resolved "object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa"
dependencies:
for-own "^0.1.4"
is-extendable "^0.1.1"
once@^1.3.0:
version "1.4.0"
resolved "once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
wrappy "1"
parse-glob@^3.0.4:
version "3.0.4"
resolved "parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
dependencies:
glob-base "^0.3.0"
is-dotfile "^1.0.0"
is-extglob "^1.0.0"
is-glob "^2.0.0"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -381,87 +168,16 @@ path-parse@^1.0.5:
version "1.0.5"
resolved "path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
preserve@^0.2.0:
version "0.2.0"
resolved "serve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
randomatic@^3.0.0:
version "3.1.1"
resolved "domatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed"
dependencies:
is-number "^4.0.0"
kind-of "^6.0.0"
math-random "^1.0.1"
regex-cache@^0.4.2:
version "0.4.4"
resolved "regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
dependencies:
is-equal-shallow "^0.1.3"
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "parator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
repeat-element@^1.1.2:
version "1.1.3"
resolved "repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
repeat-string@^1.5.2:
version "1.6.1"
resolved "repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
resolve@^1.1.6, resolve@^1.8.1:
version "1.8.1"
resolved "olve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
dependencies:
path-parse "^1.0.5"
resolve@^1.3.2:
version "1.5.0"
resolved "resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
path-parse "^1.0.5"
rollup-plugin-commonjs@^9.2.0:
version "9.2.0"
resolved "mmonjs/-/rollup-plugin-commonjs-9.2.0.tgz#4604e25069e0c78a09e08faa95dc32dec27f7c89"
dependencies:
estree-walker "^0.5.2"
magic-string "^0.25.1"
resolve "^1.8.1"
rollup-pluginutils "^2.3.3"
rollup-plugin-node-resolve@^3.4.0:
version "3.4.0"
resolved "de-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz#908585eda12e393caac7498715a01e08606abc89"
dependencies:
builtin-modules "^2.0.0"
is-module "^1.0.0"
resolve "^1.1.6"
rollup-pluginutils@^2.3.3:
version "2.3.3"
resolved "rollup-pluginutils-2.3.3.tgz#3aad9b1eb3e7fe8262820818840bf091e5ae6794"
dependencies:
estree-walker "^0.5.2"
micromatch "^2.3.11"
rollup@^0.66.6:
version "0.66.6"
resolved "lup-0.66.6.tgz#ce7d6185beb7acea644ce220c25e71ae03275482"
dependencies:
"@types/estree" "0.0.39"
"@types/node" "*"
semver@^5.3.0:
version "5.5.0"
resolved "semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
sourcemap-codec@^1.4.1:
version "1.4.3"
resolved "sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@@ -509,9 +225,9 @@ tsutils@^2.12.1:
dependencies:
tslib "^1.8.1"
typescript@3.5.1:
version "3.5.1"
resolved "typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
typescript@^3.7.2:
version "3.7.2"
resolved "typescript-3.7.2.tgz"
wrappy@1:
version "1.0.2"

View File

@@ -7,6 +7,7 @@ import com.semmle.js.ast.AssignmentExpression;
import com.semmle.js.ast.BlockStatement;
import com.semmle.js.ast.CallExpression;
import com.semmle.js.ast.CatchClause;
import com.semmle.js.ast.Chainable;
import com.semmle.js.ast.ClassExpression;
import com.semmle.js.ast.ComprehensionBlock;
import com.semmle.js.ast.ComprehensionExpression;
@@ -470,7 +471,8 @@ public class CustomParser extends FlowParser {
Expression property = this.parsePropertyIdentifierOrIdentifier();
MemberExpression node =
new MemberExpression(start, base, property, false, false, isOnOptionalChain(false, base));
new MemberExpression(
start, base, property, false, false, Chainable.isOnOptionalChain(false, base));
return Pair.make(this.finishNode(node), true);
} else if (this.eat(doubleDot)) {
SourceLocation start = new SourceLocation(startLoc);

View File

@@ -1520,10 +1520,6 @@ public class Parser {
}
}
protected boolean isOnOptionalChain(boolean optional, Expression base) {
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
}
/**
* Parse a single subscript {@code s}; if more subscripts could follow, return {@code Pair.make(s,
* true}, otherwise return {@code Pair.make(s, false)}.
@@ -1544,7 +1540,7 @@ public class Parser {
this.parseExpression(false, null),
true,
optional,
isOnOptionalChain(optional, base));
Chainable.isOnOptionalChain(optional, base));
this.expect(TokenType.bracketR);
return Pair.make(this.finishNode(node), true);
} else if (!noCalls && this.eat(TokenType.parenL)) {
@@ -1572,10 +1568,10 @@ public class Parser {
new ArrayList<>(),
exprList,
optional,
isOnOptionalChain(optional, base));
Chainable.isOnOptionalChain(optional, base));
return Pair.make(this.finishNode(node), true);
} else if (this.type == TokenType.backQuote) {
if (isOnOptionalChain(optional, base)) {
if (Chainable.isOnOptionalChain(optional, base)) {
this.raise(base, "An optional chain may not be used in a tagged template expression.");
}
TaggedTemplateExpression node =
@@ -1590,7 +1586,7 @@ public class Parser {
this.parseIdent(true),
false,
optional,
isOnOptionalChain(optional, base));
Chainable.isOnOptionalChain(optional, base));
return Pair.make(this.finishNode(node), true);
} else {
return Pair.make(base, false);
@@ -1832,7 +1828,7 @@ public class Parser {
Expression callee =
this.parseSubscripts(this.parseExprAtom(null), innerStartPos, innerStartLoc, true);
if (isOnOptionalChain(false, callee))
if (Chainable.isOnOptionalChain(false, callee))
this.raise(callee, "An optional chain may not be used in a `new` expression.");
return parseNewArguments(startLoc, callee);
@@ -2314,7 +2310,7 @@ public class Parser {
}
if (node instanceof MemberExpression) {
if (isOnOptionalChain(false, (MemberExpression) node))
if (Chainable.isOnOptionalChain(false, (MemberExpression) node))
this.raise(node, "Invalid left-hand side in assignment");
if (!isBinding) return node;
}

View File

@@ -7,4 +7,14 @@ public interface Chainable {
/** Is this on an optional chain? */
abstract boolean isOnOptionalChain();
/**
* Returns true if a chainable node is on an optional chain.
*
* @param optional true if the node in question is itself optional (has the ?. token)
* @param base the calle or base of the optional access
*/
public static boolean isOnOptionalChain(boolean optional, Expression base) {
return optional || base instanceof Chainable && ((Chainable) base).isOnOptionalChain();
}
}

View File

@@ -17,9 +17,10 @@ public class DeclarationFlags {
public static final int protected_ = 1 << 6;
public static final int optional = 1 << 7;
public static final int definiteAssignmentAssertion = 1 << 8;
public static final int declareKeyword = 1 << 9;
public static final int none = 0;
public static final int numberOfFlags = 9;
public static final int numberOfFlags = 10;
public static final List<String> names =
Arrays.asList(
@@ -31,7 +32,8 @@ public class DeclarationFlags {
"private",
"protected",
"optional",
"definiteAssignmentAssertion");
"definiteAssignmentAssertion",
"declare");
public static final List<String> relationNames =
Arrays.asList(
@@ -43,7 +45,8 @@ public class DeclarationFlags {
"hasPrivateKeyword",
"hasProtectedKeyword",
"isOptionalMember",
"hasDefiniteAssignmentAssertion");
"hasDefiniteAssignmentAssertion",
"hasDeclareKeyword");
public static boolean isComputed(int flags) {
return (flags & computed) != 0;
@@ -81,6 +84,10 @@ public class DeclarationFlags {
return (flags & definiteAssignmentAssertion) != 0;
}
public static boolean hasDeclareKeyword(int flags) {
return (flags & declareKeyword) != 0;
}
/** Returns a mask with the computed bit set to the value of <tt>enable</tt>. */
public static int getComputed(boolean enable) {
return enable ? computed : 0;
@@ -128,6 +135,11 @@ public class DeclarationFlags {
return enable ? definiteAssignmentAssertion : 0;
}
/** Returns a mask with the declare keyword bit set to the value of <tt>enable</tt>. */
public static int getDeclareKeyword(boolean enable) {
return enable ? declareKeyword : 0;
}
/** Returns true if the <tt>n</tt>th bit is set in <tt>flags</tt>. */
public static boolean hasNthFlag(int flags, int n) {
return (flags & (1 << n)) != 0;

View File

@@ -34,13 +34,13 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.NonNullAssertion;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
@@ -634,7 +634,7 @@ public class DefaultVisitor<C, R> implements Visitor<C, R> {
}
@Override
public R visit(IsTypeExpr nd, C c) {
public R visit(PredicateTypeExpr nd, C c) {
return visit((TypeExpression) nd, c);
}

View File

@@ -75,6 +75,11 @@ public abstract class MemberDefinition<V extends Expression> extends Node {
return DeclarationFlags.isReadonly(flags);
}
/** Returns true if this has the <tt>declare</tt> modifier. */
public boolean hasDeclareKeyword() {
return DeclarationFlags.hasDeclareKeyword(flags);
}
/**
* Returns the expression denoting the name of the member, or {@code null} if this is a
* call/construct signature.

View File

@@ -30,13 +30,13 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.NonNullAssertion;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
@@ -717,8 +717,12 @@ public class NodeCopier implements Visitor<Void, INode> {
}
@Override
public INode visit(IsTypeExpr nd, Void c) {
return new IsTypeExpr(visit(nd.getLoc()), copy(nd.getLeft()), copy(nd.getRight()));
public INode visit(PredicateTypeExpr nd, Void c) {
return new PredicateTypeExpr(
visit(nd.getLoc()),
copy(nd.getExpression()),
copy(nd.getTypeExpr()),
nd.hasAssertsKeyword());
}
@Override

View File

@@ -30,13 +30,13 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.NonNullAssertion;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
@@ -256,7 +256,7 @@ public interface Visitor<C, R> {
public R visit(TypeofTypeExpr nd, C c);
public R visit(IsTypeExpr nd, C c);
public R visit(PredicateTypeExpr nd, C c);
public R visit(InterfaceTypeExpr nd, C c);

View File

@@ -125,13 +125,13 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.NonNullAssertion;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
@@ -1377,9 +1377,8 @@ public class ASTExtractor {
trapwriter.addTuple("properties", methkey, c.parent, c.childIndex, kind, tostring);
locationManager.emitNodeLocation(nd, methkey);
visitAll(nd.getDecorators(), methkey, IdContext.varBind, -1, -1);
visit(nd.getKey(), methkey, 0, nd.isComputed() ? IdContext.varBind : IdContext.label);
// the initialiser expression of an instance field is evaluated as part of
// the name and initialiser expression of an instance field is evaluated as part of
// the constructor, so we adjust our syntactic context to reflect this
MethodDefinition ctor = null;
if (nd instanceof FieldDefinition && !nd.isStatic() && !ctors.isEmpty()) ctor = ctors.peek();
@@ -1388,6 +1387,7 @@ public class ASTExtractor {
constructorKey = trapwriter.localID(ctor.getValue());
contextManager.enterContainer(constructorKey);
}
visit(nd.getKey(), methkey, 0, nd.isComputed() ? IdContext.varBind : IdContext.label);
visit(nd.getValue(), methkey, 1, c.idcontext);
if (ctor != null) contextManager.leaveContainer();
@@ -1410,6 +1410,10 @@ public class ASTExtractor {
}
}
if (nd.hasDeclareKeyword()) {
trapwriter.addTuple("hasDeclareKeyword", methkey);
}
return methkey;
}
@@ -1707,10 +1711,13 @@ public class ASTExtractor {
}
@Override
public Label visit(IsTypeExpr nd, Context c) {
public Label visit(PredicateTypeExpr nd, Context c) {
Label key = super.visit(nd, c);
visit(nd.getLeft(), key, 0, IdContext.varInTypeBind);
visit(nd.getRight(), key, 1, IdContext.typeBind);
visit(nd.getExpression(), key, 0, IdContext.varInTypeBind);
visit(nd.getTypeExpr(), key, 1, IdContext.typeBind);
if (nd.hasAssertsKeyword()) {
trapwriter.addTuple("hasAssertsKeyword", key);
}
return key;
}

View File

@@ -387,6 +387,10 @@ public class AutoBuild {
patterns.add("-**/*.min.js");
patterns.add("-**/*-min.js");
// exclude `node_modules` and `bower_components`
patterns.add("-**/node_modules");
patterns.add("-**/bower_components");
String base = LGTM_SRC.toString().replace('\\', '/');
// process `$LGTM_INDEX_FILTERS`
for (String pattern : Main.NEWLINE.split(getEnvVar("LGTM_INDEX_FILTERS", ""))) {

View File

@@ -249,6 +249,7 @@ public class ExprKinds {
@Override
public Integer visit(MetaProperty nd, Void c) {
if (nd.getMeta().getName().equals("new")) return 82; // @newtargetexpr
if (nd.getMeta().getName().equals("import")) return 115; // @importmetaexpr
return 93; // @functionsentexpr
}

View File

@@ -37,7 +37,7 @@ public class Main {
* A version identifier that should be updated every time the extractor changes in such a way that
* it may produce different tuples for the same file under the same {@link ExtractorConfig}.
*/
public static final String EXTRACTOR_VERSION = "2019-10-07";
public static final String EXTRACTOR_VERSION = "2019-10-08";
public static final Pattern NEWLINE = Pattern.compile("\n");

View File

@@ -46,9 +46,9 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
import com.semmle.ts.ast.UnionTypeExpr;
@@ -660,8 +660,8 @@ public class ScopeManager {
}
@Override
public Void visit(IsTypeExpr nd, Void c) {
return nd.getRight().accept(this, c);
public Void visit(PredicateTypeExpr nd, Void c) {
return nd.getTypeExpr().accept(this, c);
}
@Override

View File

@@ -16,11 +16,11 @@ import com.semmle.ts.ast.IndexedAccessTypeExpr;
import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeParameter;
@@ -159,7 +159,7 @@ public class TypeExprKinds {
}
@Override
public Integer visit(IsTypeExpr nd, Void c) {
public Integer visit(PredicateTypeExpr nd, Void c) {
return isTypeExpr;
}

View File

@@ -160,7 +160,7 @@ public class AutoBuildTests {
addFile(false, LGTM_SRC, "tst.json");
addFile(true, LGTM_SRC, "package.json");
addFile(true, LGTM_SRC, ".eslintrc.yml");
addFile(true, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@@ -184,72 +184,72 @@ public class AutoBuildTests {
public void includeFile() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFile() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules/leftpad/index.js");
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/node_modules");
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern2() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:*/**/node_modules");
envVars.put("LGTM_INDEX_FILTERS", "exclude:*/**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "node_modules", "dep", "index.js");
addFile(false, LGTM_SRC, "node_modules", "dep", "node_modules", "depdep", "index.js");
addFile(true, LGTM_SRC, "vendor", "dep", "index.js");
addFile(false, LGTM_SRC, "vendor", "dep", "vendor", "depdep", "index.js");
runTest();
}
@Test
public void excludeFolderByPattern3() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/node_modules\n");
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByPatterns() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "exclude:foo\nexclude:**/node_modules");
envVars.put("LGTM_INDEX_FILTERS", "exclude:foo\nexclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules");
envVars.put("LGTM_INDEX_EXCLUDE", "vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName2() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules\n");
envVars.put("LGTM_INDEX_EXCLUDE", "vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeFolderByName3() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "./node_modules\n");
envVars.put("LGTM_INDEX_EXCLUDE", "./vendor\n");
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@@ -258,8 +258,8 @@ public class AutoBuildTests {
envVars.put("LGTM_INDEX_FILTERS", "exclude:**/*.js");
addFile(false, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.html");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(true, LGTM_SRC, "node_modules", "leftpad", "index.html");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "index.html");
runTest();
}
@@ -268,7 +268,7 @@ public class AutoBuildTests {
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.json");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@@ -277,16 +277,16 @@ public class AutoBuildTests {
envVars.put("LGTM_INDEX_FILTERS", "include:*.json");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "tst.json");
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@Test
public void includeAndExclude() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json\n" + "exclude:**/node_modules");
envVars.put("LGTM_INDEX_FILTERS", "include:**/*.json\n" + "exclude:**/vendor");
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "tst.json");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "tst.json");
addFile(false, LGTM_SRC, "vendor", "leftpad", "tst.json");
runTest();
}
@@ -295,7 +295,7 @@ public class AutoBuildTests {
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("thirdparty," + LGTM_SRC.resolve("node_modules"));
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar").toUri());
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
@@ -303,7 +303,7 @@ public class AutoBuildTests {
addFile(true, LGTM_SRC, "tst.js");
addFile(false, LGTM_SRC, "foo", "bar", "tst.js");
addFile(false, LGTM_SRC, ".git", "tst.js");
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
runTest();
}
@@ -324,7 +324,7 @@ public class AutoBuildTests {
Path repositoryFolders = Files.createFile(SEMMLE_DIST.resolve("repositoryFolders.csv"));
List<String> csvLines = new ArrayList<>();
csvLines.add("classification,path");
csvLines.add("thirdparty," + LGTM_SRC.resolve("node_modules"));
csvLines.add("thirdparty," + LGTM_SRC.resolve("vendor"));
csvLines.add("external," + LGTM_SRC.resolve("foo").resolve("bar"));
csvLines.add("metadata," + LGTM_SRC.resolve(".git"));
Files.write(repositoryFolders, csvLines, StandardCharsets.UTF_8);
@@ -333,7 +333,7 @@ public class AutoBuildTests {
addFile(true, LGTM_SRC, "tst.js");
addFile(true, LGTM_SRC, "foo", "bar", "tst.js");
addFile(false, LGTM_SRC, ".git", "tst.js");
addFile(true, LGTM_SRC, "node_modules", "leftpad", "tst.js");
addFile(true, LGTM_SRC, "vendor", "leftpad", "tst.js");
runTest();
}
@@ -453,13 +453,13 @@ public class AutoBuildTests {
@Test
public void includeNonExistentFile() throws IOException {
envVars.put("LGTM_INDEX_INCLUDE", "tst.js");
addFile(false, LGTM_SRC, "node_modules", "leftpad", "index.js");
addFile(false, LGTM_SRC, "vendor", "leftpad", "index.js");
runTest();
}
@Test
public void excludeNonExistentFile() throws IOException {
envVars.put("LGTM_INDEX_EXCLUDE", "node_modules/leftpad/index.js");
envVars.put("LGTM_INDEX_EXCLUDE", "vendor/leftpad/index.js");
addFile(true, LGTM_SRC, "tst.js");
runTest();
}
@@ -483,6 +483,40 @@ public class AutoBuildTests {
runTest();
}
@Test
public void nodeModulesAreExcluded() throws IOException {
addFile(true, LGTM_SRC, "index.js");
addFile(false, LGTM_SRC, "node_modules", "dep", "main.js");
addFile(false, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
runTest();
}
@Test
public void nodeModulesCanBeReincluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/node_modules");
addFile(true, LGTM_SRC, "index.js");
addFile(true, LGTM_SRC, "node_modules", "dep", "main.js");
addFile(true, LGTM_SRC, "node_modules", "dep", "node_modules", "leftpad", "index.js");
runTest();
}
@Test
public void bowerComponentsAreExcluded() throws IOException {
addFile(true, LGTM_SRC, "index.js");
addFile(false, LGTM_SRC, "bower_components", "dep", "main.js");
addFile(false, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
runTest();
}
@Test
public void bowerComponentsCanBeReincluded() throws IOException {
envVars.put("LGTM_INDEX_FILTERS", "include:**/bower_components");
addFile(true, LGTM_SRC, "index.js");
addFile(true, LGTM_SRC, "bower_components", "dep", "main.js");
addFile(true, LGTM_SRC, "bower_components", "dep", "bower_components", "leftpad", "index.js");
runTest();
}
@Test
public void customExtensions() throws IOException {
envVars.put("LGTM_INDEX_FILETYPES", ".jsm:js\n.soy:html");

View File

@@ -17,6 +17,7 @@ import com.semmle.js.ast.BlockStatement;
import com.semmle.js.ast.BreakStatement;
import com.semmle.js.ast.CallExpression;
import com.semmle.js.ast.CatchClause;
import com.semmle.js.ast.Chainable;
import com.semmle.js.ast.ClassBody;
import com.semmle.js.ast.ClassDeclaration;
import com.semmle.js.ast.ClassExpression;
@@ -126,13 +127,13 @@ import com.semmle.ts.ast.InferTypeExpr;
import com.semmle.ts.ast.InterfaceDeclaration;
import com.semmle.ts.ast.InterfaceTypeExpr;
import com.semmle.ts.ast.IntersectionTypeExpr;
import com.semmle.ts.ast.IsTypeExpr;
import com.semmle.ts.ast.KeywordTypeExpr;
import com.semmle.ts.ast.MappedTypeExpr;
import com.semmle.ts.ast.NamespaceDeclaration;
import com.semmle.ts.ast.NonNullAssertion;
import com.semmle.ts.ast.OptionalTypeExpr;
import com.semmle.ts.ast.ParenthesizedTypeExpr;
import com.semmle.ts.ast.PredicateTypeExpr;
import com.semmle.ts.ast.RestTypeExpr;
import com.semmle.ts.ast.TupleTypeExpr;
import com.semmle.ts.ast.TypeAliasDeclaration;
@@ -877,7 +878,10 @@ public class TypeScriptASTConverter {
}
Expression callee = convertChild(node, "expression");
List<ITypeExpression> typeArguments = convertChildrenAsTypes(node, "typeArguments");
CallExpression call = new CallExpression(loc, callee, typeArguments, arguments, false, false);
boolean optional = node.has("questionDotToken");
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, callee);
CallExpression call =
new CallExpression(loc, callee, typeArguments, arguments, optional, onOptionalChain);
attachResolvedSignature(call, node);
return call;
}
@@ -942,9 +946,7 @@ public class TypeScriptASTConverter {
SourceLocation bodyLoc = new SourceLocation(loc.getSource(), loc.getStart(), loc.getEnd());
advance(bodyLoc, skip);
ClassBody body = new ClassBody(bodyLoc, convertChildren(node, "members"));
if ("ClassExpression".equals(kind) || id == null) {
// Note that `export default class {}` is represented as a ClassDeclaration
// in TypeScript but we treat this as a ClassExpression.
if ("ClassExpression".equals(kind)) {
ClassExpression classExpr =
new ClassExpression(loc, id, typeParameters, superClass, superInterfaces, body);
attachSymbolInformation(classExpr.getClassDef(), node);
@@ -967,7 +969,13 @@ public class TypeScriptASTConverter {
classDecl.addDecorators(convertChildren(node, "decorators"));
advanceUntilAfter(loc, classDecl.getDecorators());
}
return fixExports(loc, classDecl);
Node exportedDecl = fixExports(loc, classDecl);
// Convert default-exported anonymous class declarations to class expressions.
if (exportedDecl instanceof ExportDefaultDeclaration && !classDecl.getClassDef().hasId()) {
return new ExportDefaultDeclaration(
exportedDecl.getLoc(), new ClassExpression(classDecl.getLoc(), classDecl.getClassDef()));
}
return exportedDecl;
}
private Node convertCommaListExpression(JsonObject node, SourceLocation loc) throws ParseError {
@@ -1104,7 +1112,9 @@ public class TypeScriptASTConverter {
throws ParseError {
Expression object = convertChild(node, "expression");
Expression property = convertChild(node, "argumentExpression");
return new MemberExpression(loc, object, property, true, false, false);
boolean optional = node.has("questionDotToken");
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, object);
return new MemberExpression(loc, object, property, true, optional, onOptionalChain);
}
private Node convertEmptyStatement(SourceLocation loc) {
@@ -1580,10 +1590,16 @@ public class TypeScriptASTConverter {
private Node convertMetaProperty(JsonObject node, SourceLocation loc) throws ParseError {
Position metaStart = loc.getStart();
String keywordKind =
syntaxKinds.get(node.getAsJsonPrimitive("keywordToken").getAsInt() + "").getAsString();
String identifier = keywordKind.equals("ImportKeyword") ? "import" : "new";
Position metaEnd =
new Position(metaStart.getLine(), metaStart.getColumn() + 3, metaStart.getOffset() + 3);
SourceLocation metaLoc = new SourceLocation("new", metaStart, metaEnd);
Identifier meta = new Identifier(metaLoc, "new");
new Position(
metaStart.getLine(),
metaStart.getColumn() + identifier.length(),
metaStart.getOffset() + identifier.length());
SourceLocation metaLoc = new SourceLocation(identifier, metaStart, metaEnd);
Identifier meta = new Identifier(metaLoc, identifier);
return new MetaProperty(loc, meta, convertChild(node, "name"));
}
@@ -1961,8 +1977,11 @@ public class TypeScriptASTConverter {
private Node convertPropertyAccessExpression(JsonObject node, SourceLocation loc)
throws ParseError {
Expression base = convertChild(node, "expression");
boolean optional = node.has("questionDotToken");
boolean onOptionalChain = Chainable.isOnOptionalChain(optional, base);
return new MemberExpression(
loc, convertChild(node, "expression"), convertChild(node, "name"), false, false, false);
loc, base, convertChild(node, "name"), false, optional, onOptionalChain);
}
private Node convertPropertyAssignment(JsonObject node, SourceLocation loc) throws ParseError {
@@ -1990,6 +2009,9 @@ public class TypeScriptASTConverter {
if (node.get("exclamationToken") != null) {
flags |= DeclarationFlags.definiteAssignmentAssertion;
}
if (hasModifier(node, "DeclareKeyword")) {
flags |= DeclarationFlags.declareKeyword;
}
FieldDefinition fieldDefinition =
new FieldDefinition(
loc,
@@ -2180,8 +2202,11 @@ public class TypeScriptASTConverter {
}
private Node convertTypePredicate(JsonObject node, SourceLocation loc) throws ParseError {
return new IsTypeExpr(
loc, convertChildAsType(node, "parameterName"), convertChildAsType(node, "type"));
return new PredicateTypeExpr(
loc,
convertChildAsType(node, "parameterName"),
convertChildAsType(node, "type"),
node.has("assertsModifier"));
}
private Node convertTypeReference(JsonObject node, SourceLocation loc) throws ParseError {

View File

@@ -1,32 +0,0 @@
package com.semmle.ts.ast;
import com.semmle.js.ast.SourceLocation;
import com.semmle.js.ast.Visitor;
/**
* A type of form <tt>E is T</tt> where <tt>E</tt> is a parameter name or <tt>this</tt> and
* <tt>T</tt> is a type.
*/
public class IsTypeExpr extends TypeExpression {
private final ITypeExpression left; // Always Identifier or KeywordTypeExpr (in case of 'this')
private final ITypeExpression right;
public IsTypeExpr(SourceLocation loc, ITypeExpression left, ITypeExpression right) {
super("IsTypeExpr", loc);
this.left = left;
this.right = right;
}
public ITypeExpression getLeft() {
return left;
}
public ITypeExpression getRight() {
return right;
}
@Override
public <C, R> R accept(Visitor<C, R> v, C c) {
return v.visit(this, c);
}
}

View File

@@ -0,0 +1,44 @@
package com.semmle.ts.ast;
import com.semmle.js.ast.SourceLocation;
import com.semmle.js.ast.Visitor;
/**
* A type of form <tt>E is T</tt>, <tt>asserts E is T</tt> or <tt>asserts E</tt> where <tt>E</tt> is
* a parameter name or <tt>this</tt> and <tt>T</tt> is a type.
*/
public class PredicateTypeExpr extends TypeExpression {
private final ITypeExpression expression;
private final ITypeExpression type;
private final boolean hasAssertsKeyword;
public PredicateTypeExpr(
SourceLocation loc,
ITypeExpression expression,
ITypeExpression type,
boolean hasAssertsKeyword) {
super("PredicateTypeExpr", loc);
this.expression = expression;
this.type = type;
this.hasAssertsKeyword = hasAssertsKeyword;
}
/** Returns the <tt>E</tt> in <tt>E is T</tt>. */
public ITypeExpression getExpression() {
return expression;
}
/** Returns the <tt>T</tt> in <tt>E is T</tt>. */
public ITypeExpression getTypeExpr() {
return type;
}
public boolean hasAssertsKeyword() {
return hasAssertsKeyword;
}
@Override
public <C, R> R accept(Visitor<C, R> v, C c) {
return v.visit(this, c);
}
}

View File

@@ -457,8 +457,7 @@ hasLocation(#20156,#20157)
#20158=*
exprs(#20158,0,#20156,0,"x")
hasLocation(#20158,#20059)
enclosingStmt(#20158,#20139)
exprContainers(#20158,#20001)
exprContainers(#20158,#20146)
literals("x","x",#20158)
#20159=*
properties(#20159,#20139,4,8,"static y;")
@@ -650,8 +649,7 @@ hasLocation(#20207,#20208)
#20209=*
exprs(#20209,0,#20207,0,"z")
hasLocation(#20209,#20128)
enclosingStmt(#20209,#20180)
exprContainers(#20209,#20001)
exprContainers(#20209,#20188)
literals("z","z",#20209)
#20210=*
entry_cfg_node(#20210,#20001)

View File

@@ -258,18 +258,17 @@ properties(#20090,#20086,2,8,"x = new.target;")
locations_default(#20091,#10000,2,3,2,17)
hasLocation(#20090,#20091)
#20092=*
exprs(#20092,0,#20090,0,"x")
hasLocation(#20092,#20031)
enclosingStmt(#20092,#20086)
exprContainers(#20092,#20001)
literals("x","x",#20092)
#20093=*
exprs(#20093,0,#20090,0,"x")
hasLocation(#20093,#20031)
exprContainers(#20093,#20092)
literals("x","x",#20093)
#20094=*
exprs(#20094,82,#20090,1,"new.target")
#20095=@"loc,{#10000},2,7,2,16"
locations_default(#20095,#10000,2,7,2,16)
hasLocation(#20094,#20095)
exprContainers(#20094,#20093)
exprContainers(#20094,#20092)
#20096=*
properties(#20096,#20086,3,0,"constructor() {}")
#20097=@"loc,{#10000},1,9,1,8"
@@ -281,21 +280,21 @@ hasLocation(#20098,#20097)
enclosingStmt(#20098,#20086)
exprContainers(#20098,#20001)
literals("constructor","constructor",#20098)
exprs(#20093,9,#20096,1,"() {}")
hasLocation(#20093,#20097)
enclosingStmt(#20093,#20086)
exprContainers(#20093,#20001)
exprs(#20092,9,#20096,1,"() {}")
hasLocation(#20092,#20097)
enclosingStmt(#20092,#20086)
exprContainers(#20092,#20001)
#20099=*
scopes(#20099,1)
scopenodes(#20093,#20099)
scopenodes(#20092,#20099)
scopenesting(#20099,#20089)
#20100=@"var;{arguments};{#20099}"
variables(#20100,"arguments",#20099)
isArgumentsObject(#20100)
#20101=*
stmts(#20101,1,#20093,-2,"{}")
stmts(#20101,1,#20092,-2,"{}")
hasLocation(#20101,#20097)
stmtContainers(#20101,#20093)
stmtContainers(#20101,#20092)
isMethod(#20096)
#20102=*
stmts(#20102,26,#20001,1,"class B ... er.x;\n}")
@@ -328,26 +327,25 @@ properties(#20107,#20102,2,8,"y = super.x;")
locations_default(#20108,#10000,6,3,6,14)
hasLocation(#20107,#20108)
#20109=*
exprs(#20109,0,#20107,0,"y")
hasLocation(#20109,#20054)
enclosingStmt(#20109,#20102)
exprContainers(#20109,#20001)
literals("y","y",#20109)
#20110=*
exprs(#20110,0,#20107,0,"y")
hasLocation(#20110,#20054)
exprContainers(#20110,#20109)
literals("y","y",#20110)
#20111=*
exprs(#20111,14,#20107,1,"super.x")
#20112=@"loc,{#10000},6,7,6,13"
locations_default(#20112,#10000,6,7,6,13)
hasLocation(#20111,#20112)
exprContainers(#20111,#20110)
exprContainers(#20111,#20109)
#20113=*
exprs(#20113,81,#20111,0,"super")
hasLocation(#20113,#20058)
exprContainers(#20113,#20110)
exprContainers(#20113,#20109)
#20114=*
exprs(#20114,0,#20111,1,"x")
hasLocation(#20114,#20062)
exprContainers(#20114,#20110)
exprContainers(#20114,#20109)
literals("x","x",#20114)
#20115=*
properties(#20115,#20102,3,0,"constru ... rgs); }")
@@ -360,54 +358,54 @@ hasLocation(#20117,#20116)
enclosingStmt(#20117,#20102)
exprContainers(#20117,#20001)
literals("constructor","constructor",#20117)
exprs(#20110,9,#20115,1,"(...arg ... rgs); }")
hasLocation(#20110,#20116)
enclosingStmt(#20110,#20102)
exprContainers(#20110,#20001)
exprs(#20109,9,#20115,1,"(...arg ... rgs); }")
hasLocation(#20109,#20116)
enclosingStmt(#20109,#20102)
exprContainers(#20109,#20001)
#20118=*
scopes(#20118,1)
scopenodes(#20110,#20118)
scopenodes(#20109,#20118)
scopenesting(#20118,#20105)
#20119=@"var;{args};{#20118}"
variables(#20119,"args",#20118)
#20120=*
exprs(#20120,78,#20110,0,"args")
exprs(#20120,78,#20109,0,"args")
hasLocation(#20120,#20116)
exprContainers(#20120,#20110)
exprContainers(#20120,#20109)
literals("args","args",#20120)
decl(#20120,#20119)
#20121=@"var;{arguments};{#20118}"
variables(#20121,"arguments",#20118)
isArgumentsObject(#20121)
hasRestParameter(#20110)
hasRestParameter(#20109)
#20122=*
stmts(#20122,1,#20110,-2,"{ super(...args); }")
stmts(#20122,1,#20109,-2,"{ super(...args); }")
hasLocation(#20122,#20116)
stmtContainers(#20122,#20110)
stmtContainers(#20122,#20109)
#20123=*
stmts(#20123,2,#20122,0,"super(...args);")
hasLocation(#20123,#20116)
stmtContainers(#20123,#20110)
stmtContainers(#20123,#20109)
#20124=*
exprs(#20124,13,#20123,0,"super(...args)")
hasLocation(#20124,#20116)
enclosingStmt(#20124,#20123)
exprContainers(#20124,#20110)
exprContainers(#20124,#20109)
#20125=*
exprs(#20125,81,#20124,-1,"super")
hasLocation(#20125,#20116)
enclosingStmt(#20125,#20123)
exprContainers(#20125,#20110)
exprContainers(#20125,#20109)
#20126=*
exprs(#20126,66,#20124,0,"...args")
hasLocation(#20126,#20116)
enclosingStmt(#20126,#20123)
exprContainers(#20126,#20110)
exprContainers(#20126,#20109)
#20127=*
exprs(#20127,79,#20126,0,"args")
hasLocation(#20127,#20116)
enclosingStmt(#20127,#20123)
exprContainers(#20127,#20110)
exprContainers(#20127,#20109)
literals("args","args",#20127)
bind(#20127,#20119)
isMethod(#20115)
@@ -435,12 +433,11 @@ properties(#20132,#20128,2,8,"async;")
locations_default(#20133,#10000,10,3,10,8)
hasLocation(#20132,#20133)
#20134=*
exprs(#20134,0,#20132,0,"async")
hasLocation(#20134,#20073)
enclosingStmt(#20134,#20128)
exprContainers(#20134,#20001)
literals("async","async",#20134)
#20135=*
exprs(#20135,0,#20132,0,"async")
hasLocation(#20135,#20073)
exprContainers(#20135,#20134)
literals("async","async",#20135)
#20136=*
properties(#20136,#20128,3,0,"constructor() {}")
#20137=@"loc,{#10000},9,9,9,8"
@@ -452,21 +449,21 @@ hasLocation(#20138,#20137)
enclosingStmt(#20138,#20128)
exprContainers(#20138,#20001)
literals("constructor","constructor",#20138)
exprs(#20135,9,#20136,1,"() {}")
hasLocation(#20135,#20137)
enclosingStmt(#20135,#20128)
exprContainers(#20135,#20001)
exprs(#20134,9,#20136,1,"() {}")
hasLocation(#20134,#20137)
enclosingStmt(#20134,#20128)
exprContainers(#20134,#20001)
#20139=*
scopes(#20139,1)
scopenodes(#20135,#20139)
scopenodes(#20134,#20139)
scopenesting(#20139,#20131)
#20140=@"var;{arguments};{#20139}"
variables(#20140,"arguments",#20139)
isArgumentsObject(#20140)
#20141=*
stmts(#20141,1,#20135,-2,"{}")
stmts(#20141,1,#20134,-2,"{}")
hasLocation(#20141,#20137)
stmtContainers(#20141,#20135)
stmtContainers(#20141,#20134)
isMethod(#20136)
#20142=*
entry_cfg_node(#20142,#20001)
@@ -476,31 +473,31 @@ hasLocation(#20142,#20143)
#20144=*
exit_cfg_node(#20144,#20001)
hasLocation(#20144,#20078)
successor(#20134,#20132)
successor(#20135,#20136)
successor(#20135,#20132)
successor(#20134,#20136)
#20145=*
entry_cfg_node(#20145,#20135)
entry_cfg_node(#20145,#20134)
hasLocation(#20145,#20137)
successor(#20132,#20141)
#20146=*
exit_cfg_node(#20146,#20135)
exit_cfg_node(#20146,#20134)
hasLocation(#20146,#20137)
successor(#20141,#20146)
successor(#20145,#20134)
successor(#20138,#20135)
successor(#20145,#20135)
successor(#20138,#20134)
successor(#20136,#20128)
successor(#20130,#20138)
successor(#20128,#20144)
successor(#20114,#20111)
successor(#20113,#20114)
successor(#20111,#20107)
successor(#20109,#20113)
successor(#20110,#20115)
successor(#20110,#20113)
successor(#20109,#20115)
#20147=*
entry_cfg_node(#20147,#20110)
entry_cfg_node(#20147,#20109)
hasLocation(#20147,#20116)
#20148=*
exit_cfg_node(#20148,#20110)
exit_cfg_node(#20148,#20109)
hasLocation(#20148,#20116)
successor(#20122,#20123)
successor(#20123,#20125)
@@ -508,27 +505,27 @@ successor(#20127,#20126)
successor(#20126,#20124)
successor(#20125,#20127)
successor(#20107,#20148)
successor(#20124,#20109)
successor(#20124,#20110)
successor(#20120,#20122)
successor(#20147,#20120)
successor(#20117,#20110)
successor(#20117,#20109)
successor(#20115,#20102)
successor(#20106,#20117)
successor(#20104,#20106)
successor(#20102,#20130)
successor(#20094,#20090)
successor(#20092,#20094)
successor(#20093,#20096)
successor(#20093,#20094)
successor(#20092,#20096)
#20149=*
entry_cfg_node(#20149,#20093)
entry_cfg_node(#20149,#20092)
hasLocation(#20149,#20097)
successor(#20090,#20101)
#20150=*
exit_cfg_node(#20150,#20093)
exit_cfg_node(#20150,#20092)
hasLocation(#20150,#20097)
successor(#20101,#20150)
successor(#20149,#20092)
successor(#20098,#20093)
successor(#20149,#20093)
successor(#20098,#20092)
successor(#20096,#20086)
successor(#20088,#20098)
successor(#20086,#20104)

View File

@@ -542,12 +542,11 @@ properties(#20197,#20193,2,8,"x: T;")
locations_default(#20198,#10000,7,3,7,7)
hasLocation(#20197,#20198)
#20199=*
exprs(#20199,0,#20197,0,"x")
hasLocation(#20199,#20082)
enclosingStmt(#20199,#20193)
exprContainers(#20199,#20001)
literals("x","x",#20199)
#20200=*
exprs(#20200,0,#20197,0,"x")
hasLocation(#20200,#20082)
exprContainers(#20200,#20199)
literals("x","x",#20200)
#20201=*
properties(#20201,#20193,3,0,"m() : Point {}")
#20202=@"loc,{#10000},8,3,8,16"
@@ -591,21 +590,21 @@ hasLocation(#20212,#20211)
enclosingStmt(#20212,#20193)
exprContainers(#20212,#20001)
literals("constructor","constructor",#20212)
exprs(#20200,9,#20210,1,"() {}")
hasLocation(#20200,#20211)
enclosingStmt(#20200,#20193)
exprContainers(#20200,#20001)
exprs(#20199,9,#20210,1,"() {}")
hasLocation(#20199,#20211)
enclosingStmt(#20199,#20193)
exprContainers(#20199,#20001)
#20213=*
scopes(#20213,1)
scopenodes(#20200,#20213)
scopenodes(#20199,#20213)
scopenesting(#20213,#20196)
#20214=@"var;{arguments};{#20213}"
variables(#20214,"arguments",#20213)
isArgumentsObject(#20214)
#20215=*
stmts(#20215,1,#20200,-2,"{}")
stmts(#20215,1,#20199,-2,"{}")
hasLocation(#20215,#20211)
stmtContainers(#20215,#20200)
stmtContainers(#20215,#20199)
isMethod(#20210)
#20216=*
stmts(#20216,17,#20001,1,"functio ... :\n }\n}")
@@ -692,18 +691,18 @@ successor(#20227,#20229)
successor(#20229,#20231)
successor(#20231,#20237)
successor(#20235,#20221)
successor(#20199,#20197)
successor(#20200,#20210)
successor(#20200,#20197)
successor(#20199,#20210)
#20239=*
entry_cfg_node(#20239,#20200)
entry_cfg_node(#20239,#20199)
hasLocation(#20239,#20211)
successor(#20197,#20215)
#20240=*
exit_cfg_node(#20240,#20200)
exit_cfg_node(#20240,#20199)
hasLocation(#20240,#20211)
successor(#20215,#20240)
successor(#20239,#20199)
successor(#20212,#20200)
successor(#20239,#20200)
successor(#20212,#20199)
successor(#20210,#20193)
successor(#20204,#20201)
#20241=*

View File

@@ -194,12 +194,11 @@ properties(#20065,#20061,2,8,"+x: int")
locations_default(#20066,#10000,6,3,6,9)
hasLocation(#20065,#20066)
#20067=*
exprs(#20067,0,#20065,0,"x")
hasLocation(#20067,#20049)
enclosingStmt(#20067,#20061)
exprContainers(#20067,#20001)
literals("x","x",#20067)
#20068=*
exprs(#20068,0,#20065,0,"x")
hasLocation(#20068,#20049)
exprContainers(#20068,#20067)
literals("x","x",#20068)
#20069=*
properties(#20069,#20061,3,0,"constructor() {}")
#20070=@"loc,{#10000},5,11,5,10"
@@ -211,21 +210,21 @@ hasLocation(#20071,#20070)
enclosingStmt(#20071,#20061)
exprContainers(#20071,#20001)
literals("constructor","constructor",#20071)
exprs(#20068,9,#20069,1,"() {}")
hasLocation(#20068,#20070)
enclosingStmt(#20068,#20061)
exprContainers(#20068,#20001)
exprs(#20067,9,#20069,1,"() {}")
hasLocation(#20067,#20070)
enclosingStmt(#20067,#20061)
exprContainers(#20067,#20001)
#20072=*
scopes(#20072,1)
scopenodes(#20068,#20072)
scopenodes(#20067,#20072)
scopenesting(#20072,#20064)
#20073=@"var;{arguments};{#20072}"
variables(#20073,"arguments",#20072)
isArgumentsObject(#20073)
#20074=*
stmts(#20074,1,#20068,-2,"{}")
stmts(#20074,1,#20067,-2,"{}")
hasLocation(#20074,#20070)
stmtContainers(#20074,#20068)
stmtContainers(#20074,#20067)
isMethod(#20069)
#20075=*
entry_cfg_node(#20075,#20001)
@@ -235,18 +234,18 @@ hasLocation(#20075,#20076)
#20077=*
exit_cfg_node(#20077,#20001)
hasLocation(#20077,#20056)
successor(#20067,#20065)
successor(#20068,#20069)
successor(#20068,#20065)
successor(#20067,#20069)
#20078=*
entry_cfg_node(#20078,#20068)
entry_cfg_node(#20078,#20067)
hasLocation(#20078,#20070)
successor(#20065,#20074)
#20079=*
exit_cfg_node(#20079,#20068)
exit_cfg_node(#20079,#20067)
hasLocation(#20079,#20070)
successor(#20074,#20079)
successor(#20078,#20067)
successor(#20071,#20068)
successor(#20078,#20068)
successor(#20071,#20067)
successor(#20069,#20061)
successor(#20063,#20071)
successor(#20061,#20077)

View File

@@ -0,0 +1 @@
export class {}

View File

@@ -0,0 +1,3 @@
base?.x.y;
base?.(x).y;
base?.[z].y;

View File

@@ -190,16 +190,15 @@ properties(#20065,#20062,2,8,"y = 42;")
locations_default(#20066,#10000,2,3,2,9)
hasLocation(#20065,#20066)
#20067=*
exprs(#20067,0,#20065,0,"y")
hasLocation(#20067,#20019)
enclosingStmt(#20067,#20062)
exprContainers(#20067,#20001)
literals("y","y",#20067)
#20068=*
exprs(#20068,0,#20065,0,"y")
hasLocation(#20068,#20019)
exprContainers(#20068,#20067)
literals("y","y",#20068)
#20069=*
exprs(#20069,3,#20065,1,"42")
hasLocation(#20069,#20023)
exprContainers(#20069,#20068)
exprContainers(#20069,#20067)
literals("42","42",#20069)
#20070=*
properties(#20070,#20062,3,0,"constru ... tring);")
@@ -212,29 +211,29 @@ hasLocation(#20072,#20071)
enclosingStmt(#20072,#20062)
exprContainers(#20072,#20001)
literals("constructor","constructor",#20072)
exprs(#20068,9,#20070,1,"constru ... tring);")
hasLocation(#20068,#20071)
enclosingStmt(#20068,#20062)
exprContainers(#20068,#20001)
exprs(#20067,9,#20070,1,"constru ... tring);")
hasLocation(#20067,#20071)
enclosingStmt(#20067,#20062)
exprContainers(#20067,#20001)
#20073=*
scopes(#20073,1)
scopenodes(#20068,#20073)
scopenodes(#20067,#20073)
scopenesting(#20073,#20064)
#20074=@"var;{x};{#20073}"
variables(#20074,"x",#20073)
#20075=*
exprs(#20075,78,#20068,0,"x")
exprs(#20075,78,#20067,0,"x")
hasLocation(#20075,#20031)
exprContainers(#20075,#20068)
exprContainers(#20075,#20067)
literals("x","x",#20075)
decl(#20075,#20074)
#20076=@"var;{arguments};{#20073}"
variables(#20076,"arguments",#20073)
isArgumentsObject(#20076)
#20077=*
typeexprs(#20077,2,#20068,-6,"string")
typeexprs(#20077,2,#20067,-6,"string")
hasLocation(#20077,#20035)
exprContainers(#20077,#20068)
exprContainers(#20077,#20067)
literals("string","string",#20077)
isMethod(#20070)
#20078=*
@@ -289,7 +288,7 @@ hasLocation(#20089,#20090)
exit_cfg_node(#20091,#20001)
hasLocation(#20091,#20058)
successor(#20069,#20065)
successor(#20067,#20069)
successor(#20068,#20069)
successor(#20081,#20078)
#20092=*
entry_cfg_node(#20092,#20081)
@@ -303,7 +302,7 @@ exit_cfg_node(#20094,#20081)
locations_default(#20095,#10000,4,24,4,23)
hasLocation(#20094,#20095)
successor(#20087,#20094)
successor(#20084,#20067)
successor(#20084,#20068)
successor(#20092,#20084)
successor(#20080,#20081)
successor(#20078,#20062)

View File

@@ -450,12 +450,11 @@ literals("fieldDecorator","fieldDecorator",#20148)
variables(#20149,"fieldDecorator",#20000)
bind(#20148,#20149)
#20150=*
exprs(#20150,0,#20144,0,"field")
hasLocation(#20150,#20071)
enclosingStmt(#20150,#20118)
exprContainers(#20150,#20001)
literals("field","field",#20150)
#20151=*
exprs(#20151,0,#20144,0,"field")
hasLocation(#20151,#20071)
exprContainers(#20151,#20150)
literals("field","field",#20151)
#20152=*
typeexprs(#20152,2,#20144,2,"number")
hasLocation(#20152,#20075)
@@ -473,21 +472,21 @@ hasLocation(#20155,#20154)
enclosingStmt(#20155,#20118)
exprContainers(#20155,#20001)
literals("constructor","constructor",#20155)
exprs(#20151,9,#20153,1,"() {}")
hasLocation(#20151,#20154)
enclosingStmt(#20151,#20118)
exprContainers(#20151,#20001)
exprs(#20150,9,#20153,1,"() {}")
hasLocation(#20150,#20154)
enclosingStmt(#20150,#20118)
exprContainers(#20150,#20001)
#20156=*
scopes(#20156,1)
scopenodes(#20151,#20156)
scopenodes(#20150,#20156)
scopenesting(#20156,#20124)
#20157=@"var;{arguments};{#20156}"
variables(#20157,"arguments",#20156)
isArgumentsObject(#20157)
#20158=*
stmts(#20158,1,#20151,-2,"{}")
stmts(#20158,1,#20150,-2,"{}")
hasLocation(#20158,#20154)
stmtContainers(#20158,#20151)
stmtContainers(#20158,#20150)
isMethod(#20153)
#20159=*
stmts(#20159,17,#20001,1,"@functi ... un() {}")
@@ -615,18 +614,18 @@ locations_default(#20189,#10000,11,18,11,17)
hasLocation(#20188,#20189)
successor(#20164,#20188)
successor(#20186,#20164)
successor(#20150,#20144)
successor(#20151,#20153)
successor(#20151,#20144)
successor(#20150,#20153)
#20190=*
entry_cfg_node(#20190,#20151)
entry_cfg_node(#20190,#20150)
hasLocation(#20190,#20154)
successor(#20144,#20158)
#20191=*
exit_cfg_node(#20191,#20151)
exit_cfg_node(#20191,#20150)
hasLocation(#20191,#20154)
successor(#20158,#20191)
successor(#20190,#20150)
successor(#20155,#20151)
successor(#20190,#20151)
successor(#20155,#20150)
successor(#20153,#20118)
successor(#20132,#20125)
#20192=*

View File

@@ -0,0 +1,114 @@
#10000=@"/exportclass.ts;sourcefile"
files(#10000,"/exportclass.ts","exportclass","ts",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
#20002=*
lines(#20002,#20001,"export class {}","
")
#20003=@"loc,{#10000},1,1,1,15"
locations_default(#20003,#10000,1,1,1,15)
hasLocation(#20002,#20003)
numlines(#20001,1,1,0)
#20004=*
tokeninfo(#20004,7,#20001,0,"export")
#20005=@"loc,{#10000},1,1,1,6"
locations_default(#20005,#10000,1,1,1,6)
hasLocation(#20004,#20005)
#20006=*
tokeninfo(#20006,7,#20001,1,"class")
#20007=@"loc,{#10000},1,8,1,12"
locations_default(#20007,#10000,1,8,1,12)
hasLocation(#20006,#20007)
#20008=*
tokeninfo(#20008,8,#20001,2,"{")
#20009=@"loc,{#10000},1,14,1,14"
locations_default(#20009,#10000,1,14,1,14)
hasLocation(#20008,#20009)
#20010=*
tokeninfo(#20010,8,#20001,3,"}")
#20011=@"loc,{#10000},1,15,1,15"
locations_default(#20011,#10000,1,15,1,15)
hasLocation(#20010,#20011)
#20012=*
tokeninfo(#20012,0,#20001,4,"")
#20013=@"loc,{#10000},2,1,2,0"
locations_default(#20013,#10000,2,1,2,0)
hasLocation(#20012,#20013)
toplevels(#20001,0)
#20014=@"loc,{#10000},1,1,2,0"
locations_default(#20014,#10000,1,1,2,0)
hasLocation(#20001,#20014)
#20015=@"module;{#10000},1,1"
scopes(#20015,3)
scopenodes(#20001,#20015)
scopenesting(#20015,#20000)
isModule(#20001)
isES2015Module(#20001)
#20016=*
stmts(#20016,30,#20001,0,"export class {}")
hasLocation(#20016,#20003)
stmtContainers(#20016,#20001)
#20017=*
stmts(#20017,26,#20016,-1,"class {}")
#20018=@"loc,{#10000},1,8,1,15"
locations_default(#20018,#10000,1,8,1,15)
hasLocation(#20017,#20018)
stmtContainers(#20017,#20001)
#20019=*
properties(#20019,#20017,2,0,"constructor() {}")
#20020=@"loc,{#10000},1,6,1,5"
locations_default(#20020,#10000,1,6,1,5)
hasLocation(#20019,#20020)
#20021=*
exprs(#20021,0,#20019,0,"constructor")
hasLocation(#20021,#20020)
enclosingStmt(#20021,#20017)
exprContainers(#20021,#20001)
literals("constructor","constructor",#20021)
#20022=*
exprs(#20022,9,#20019,1,"() {}")
hasLocation(#20022,#20020)
enclosingStmt(#20022,#20017)
exprContainers(#20022,#20001)
#20023=*
scopes(#20023,1)
scopenodes(#20022,#20023)
scopenesting(#20023,#20015)
#20024=@"var;{arguments};{#20023}"
variables(#20024,"arguments",#20023)
isArgumentsObject(#20024)
#20025=*
stmts(#20025,1,#20022,-2,"{}")
hasLocation(#20025,#20020)
stmtContainers(#20025,#20022)
isMethod(#20019)
#20026=*
entry_cfg_node(#20026,#20001)
#20027=@"loc,{#10000},1,1,1,0"
locations_default(#20027,#10000,1,1,1,0)
hasLocation(#20026,#20027)
#20028=*
exit_cfg_node(#20028,#20001)
hasLocation(#20028,#20013)
successor(#20022,#20019)
#20029=*
entry_cfg_node(#20029,#20022)
hasLocation(#20029,#20020)
#20030=*
exit_cfg_node(#20030,#20022)
hasLocation(#20030,#20020)
successor(#20025,#20030)
successor(#20029,#20025)
successor(#20021,#20022)
successor(#20019,#20017)
successor(#20017,#20028)
successor(#20026,#20016)
numlines(#10000,1,1,0)
filetype(#10000,"typescript")

View File

@@ -895,12 +895,11 @@ properties(#20301,#20260,6,8,"abstract x: number;")
locations_default(#20302,#10000,14,3,14,21)
hasLocation(#20301,#20302)
#20303=*
exprs(#20303,0,#20301,0,"x")
hasLocation(#20303,#20159)
enclosingStmt(#20303,#20260)
exprContainers(#20303,#20001)
literals("x","x",#20303)
#20304=*
exprs(#20304,0,#20301,0,"x")
hasLocation(#20304,#20159)
exprContainers(#20304,#20303)
literals("x","x",#20304)
isAbstractMember(#20301)
#20305=*
typeexprs(#20305,2,#20301,2,"number")
@@ -919,21 +918,21 @@ hasLocation(#20308,#20307)
enclosingStmt(#20308,#20260)
exprContainers(#20308,#20001)
literals("constructor","constructor",#20308)
exprs(#20304,9,#20306,1,"() {}")
hasLocation(#20304,#20307)
enclosingStmt(#20304,#20260)
exprContainers(#20304,#20001)
exprs(#20303,9,#20306,1,"() {}")
hasLocation(#20303,#20307)
enclosingStmt(#20303,#20260)
exprContainers(#20303,#20001)
#20309=*
scopes(#20309,1)
scopenodes(#20304,#20309)
scopenodes(#20303,#20309)
scopenesting(#20309,#20263)
#20310=@"var;{arguments};{#20309}"
variables(#20310,"arguments",#20309)
isArgumentsObject(#20310)
#20311=*
stmts(#20311,1,#20304,-2,"{}")
stmts(#20311,1,#20303,-2,"{}")
hasLocation(#20311,#20307)
stmtContainers(#20311,#20304)
stmtContainers(#20311,#20303)
isMethod(#20306)
#20312=*
stmts(#20312,26,#20001,2,"declare ... mber;\n}")
@@ -1114,12 +1113,11 @@ properties(#20354,#20312,6,8,"abstract x: number;")
locations_default(#20355,#10000,28,3,28,21)
hasLocation(#20354,#20355)
#20356=*
exprs(#20356,0,#20354,0,"x")
hasLocation(#20356,#20242)
enclosingStmt(#20356,#20312)
exprContainers(#20356,#20001)
literals("x","x",#20356)
#20357=*
exprs(#20357,0,#20354,0,"x")
hasLocation(#20357,#20242)
exprContainers(#20357,#20356)
literals("x","x",#20357)
isAbstractMember(#20354)
#20358=*
typeexprs(#20358,2,#20354,2,"number")
@@ -1138,21 +1136,21 @@ hasLocation(#20361,#20360)
enclosingStmt(#20361,#20312)
exprContainers(#20361,#20001)
literals("constructor","constructor",#20361)
exprs(#20357,9,#20359,1,"() {}")
hasLocation(#20357,#20360)
enclosingStmt(#20357,#20312)
exprContainers(#20357,#20001)
exprs(#20356,9,#20359,1,"() {}")
hasLocation(#20356,#20360)
enclosingStmt(#20356,#20312)
exprContainers(#20356,#20001)
#20362=*
scopes(#20362,1)
scopenodes(#20357,#20362)
scopenodes(#20356,#20362)
scopenesting(#20362,#20316)
#20363=@"var;{arguments};{#20362}"
variables(#20363,"arguments",#20362)
isArgumentsObject(#20363)
#20364=*
stmts(#20364,1,#20357,-2,"{}")
stmts(#20364,1,#20356,-2,"{}")
hasLocation(#20364,#20360)
stmtContainers(#20364,#20357)
stmtContainers(#20364,#20356)
isMethod(#20359)
#20365=*
entry_cfg_node(#20365,#20001)
@@ -1163,16 +1161,16 @@ hasLocation(#20365,#20366)
exit_cfg_node(#20367,#20001)
hasLocation(#20367,#20251)
successor(#20312,#20367)
successor(#20304,#20306)
successor(#20303,#20306)
#20368=*
entry_cfg_node(#20368,#20304)
entry_cfg_node(#20368,#20303)
hasLocation(#20368,#20307)
#20369=*
exit_cfg_node(#20369,#20304)
exit_cfg_node(#20369,#20303)
hasLocation(#20369,#20307)
successor(#20311,#20369)
successor(#20368,#20311)
successor(#20308,#20304)
successor(#20308,#20303)
successor(#20306,#20260)
successor(#20293,#20290)
#20370=*

View File

@@ -0,0 +1,303 @@
#10000=@"/optionalChaining.ts;sourcefile"
files(#10000,"/optionalChaining.ts","optionalChaining","ts",0)
#10001=@"/;folder"
folders(#10001,"/","")
containerparent(#10001,#10000)
#10002=@"loc,{#10000},0,0,0,0"
locations_default(#10002,#10000,0,0,0,0)
hasLocation(#10000,#10002)
#20000=@"global_scope"
scopes(#20000,0)
#20001=@"script;{#10000},1,1"
#20002=*
lines(#20002,#20001,"base?.x.y;","
")
#20003=@"loc,{#10000},1,1,1,10"
locations_default(#20003,#10000,1,1,1,10)
hasLocation(#20002,#20003)
#20004=*
lines(#20004,#20001,"base?.(x).y;","
")
#20005=@"loc,{#10000},2,1,2,12"
locations_default(#20005,#10000,2,1,2,12)
hasLocation(#20004,#20005)
#20006=*
lines(#20006,#20001,"base?.[z].y;","
")
#20007=@"loc,{#10000},3,1,3,12"
locations_default(#20007,#10000,3,1,3,12)
hasLocation(#20006,#20007)
numlines(#20001,3,3,0)
#20008=*
tokeninfo(#20008,6,#20001,0,"base")
#20009=@"loc,{#10000},1,1,1,4"
locations_default(#20009,#10000,1,1,1,4)
hasLocation(#20008,#20009)
#20010=*
tokeninfo(#20010,8,#20001,1,"?.")
#20011=@"loc,{#10000},1,5,1,6"
locations_default(#20011,#10000,1,5,1,6)
hasLocation(#20010,#20011)
#20012=*
tokeninfo(#20012,6,#20001,2,"x")
#20013=@"loc,{#10000},1,7,1,7"
locations_default(#20013,#10000,1,7,1,7)
hasLocation(#20012,#20013)
#20014=*
tokeninfo(#20014,8,#20001,3,".")
#20015=@"loc,{#10000},1,8,1,8"
locations_default(#20015,#10000,1,8,1,8)
hasLocation(#20014,#20015)
#20016=*
tokeninfo(#20016,6,#20001,4,"y")
#20017=@"loc,{#10000},1,9,1,9"
locations_default(#20017,#10000,1,9,1,9)
hasLocation(#20016,#20017)
#20018=*
tokeninfo(#20018,8,#20001,5,";")
#20019=@"loc,{#10000},1,10,1,10"
locations_default(#20019,#10000,1,10,1,10)
hasLocation(#20018,#20019)
#20020=*
tokeninfo(#20020,6,#20001,6,"base")
#20021=@"loc,{#10000},2,1,2,4"
locations_default(#20021,#10000,2,1,2,4)
hasLocation(#20020,#20021)
#20022=*
tokeninfo(#20022,8,#20001,7,"?.")
#20023=@"loc,{#10000},2,5,2,6"
locations_default(#20023,#10000,2,5,2,6)
hasLocation(#20022,#20023)
#20024=*
tokeninfo(#20024,8,#20001,8,"(")
#20025=@"loc,{#10000},2,7,2,7"
locations_default(#20025,#10000,2,7,2,7)
hasLocation(#20024,#20025)
#20026=*
tokeninfo(#20026,6,#20001,9,"x")
#20027=@"loc,{#10000},2,8,2,8"
locations_default(#20027,#10000,2,8,2,8)
hasLocation(#20026,#20027)
#20028=*
tokeninfo(#20028,8,#20001,10,")")
#20029=@"loc,{#10000},2,9,2,9"
locations_default(#20029,#10000,2,9,2,9)
hasLocation(#20028,#20029)
#20030=*
tokeninfo(#20030,8,#20001,11,".")
#20031=@"loc,{#10000},2,10,2,10"
locations_default(#20031,#10000,2,10,2,10)
hasLocation(#20030,#20031)
#20032=*
tokeninfo(#20032,6,#20001,12,"y")
#20033=@"loc,{#10000},2,11,2,11"
locations_default(#20033,#10000,2,11,2,11)
hasLocation(#20032,#20033)
#20034=*
tokeninfo(#20034,8,#20001,13,";")
#20035=@"loc,{#10000},2,12,2,12"
locations_default(#20035,#10000,2,12,2,12)
hasLocation(#20034,#20035)
#20036=*
tokeninfo(#20036,6,#20001,14,"base")
#20037=@"loc,{#10000},3,1,3,4"
locations_default(#20037,#10000,3,1,3,4)
hasLocation(#20036,#20037)
#20038=*
tokeninfo(#20038,8,#20001,15,"?.")
#20039=@"loc,{#10000},3,5,3,6"
locations_default(#20039,#10000,3,5,3,6)
hasLocation(#20038,#20039)
#20040=*
tokeninfo(#20040,8,#20001,16,"[")
#20041=@"loc,{#10000},3,7,3,7"
locations_default(#20041,#10000,3,7,3,7)
hasLocation(#20040,#20041)
#20042=*
tokeninfo(#20042,6,#20001,17,"z")
#20043=@"loc,{#10000},3,8,3,8"
locations_default(#20043,#10000,3,8,3,8)
hasLocation(#20042,#20043)
#20044=*
tokeninfo(#20044,8,#20001,18,"]")
#20045=@"loc,{#10000},3,9,3,9"
locations_default(#20045,#10000,3,9,3,9)
hasLocation(#20044,#20045)
#20046=*
tokeninfo(#20046,8,#20001,19,".")
#20047=@"loc,{#10000},3,10,3,10"
locations_default(#20047,#10000,3,10,3,10)
hasLocation(#20046,#20047)
#20048=*
tokeninfo(#20048,6,#20001,20,"y")
#20049=@"loc,{#10000},3,11,3,11"
locations_default(#20049,#10000,3,11,3,11)
hasLocation(#20048,#20049)
#20050=*
tokeninfo(#20050,8,#20001,21,";")
#20051=@"loc,{#10000},3,12,3,12"
locations_default(#20051,#10000,3,12,3,12)
hasLocation(#20050,#20051)
#20052=*
tokeninfo(#20052,0,#20001,22,"")
#20053=@"loc,{#10000},4,1,4,0"
locations_default(#20053,#10000,4,1,4,0)
hasLocation(#20052,#20053)
toplevels(#20001,0)
#20054=@"loc,{#10000},1,1,4,0"
locations_default(#20054,#10000,1,1,4,0)
hasLocation(#20001,#20054)
#20055=*
stmts(#20055,2,#20001,0,"base?.x.y;")
hasLocation(#20055,#20003)
stmtContainers(#20055,#20001)
#20056=*
exprs(#20056,14,#20055,0,"base?.x.y")
#20057=@"loc,{#10000},1,1,1,9"
locations_default(#20057,#10000,1,1,1,9)
hasLocation(#20056,#20057)
enclosingStmt(#20056,#20055)
exprContainers(#20056,#20001)
#20058=*
exprs(#20058,14,#20056,0,"base?.x")
#20059=@"loc,{#10000},1,1,1,7"
locations_default(#20059,#10000,1,1,1,7)
hasLocation(#20058,#20059)
enclosingStmt(#20058,#20055)
exprContainers(#20058,#20001)
#20060=*
exprs(#20060,79,#20058,0,"base")
hasLocation(#20060,#20009)
enclosingStmt(#20060,#20055)
exprContainers(#20060,#20001)
literals("base","base",#20060)
#20061=@"var;{base};{#20000}"
variables(#20061,"base",#20000)
bind(#20060,#20061)
#20062=*
exprs(#20062,0,#20058,1,"x")
hasLocation(#20062,#20013)
enclosingStmt(#20062,#20055)
exprContainers(#20062,#20001)
literals("x","x",#20062)
isOptionalChaining(#20058)
#20063=*
exprs(#20063,0,#20056,1,"y")
hasLocation(#20063,#20017)
enclosingStmt(#20063,#20055)
exprContainers(#20063,#20001)
literals("y","y",#20063)
#20064=*
stmts(#20064,2,#20001,1,"base?.(x).y;")
hasLocation(#20064,#20005)
stmtContainers(#20064,#20001)
#20065=*
exprs(#20065,14,#20064,0,"base?.(x).y")
#20066=@"loc,{#10000},2,1,2,11"
locations_default(#20066,#10000,2,1,2,11)
hasLocation(#20065,#20066)
enclosingStmt(#20065,#20064)
exprContainers(#20065,#20001)
#20067=*
exprs(#20067,13,#20065,0,"base?.(x)")
#20068=@"loc,{#10000},2,1,2,9"
locations_default(#20068,#10000,2,1,2,9)
hasLocation(#20067,#20068)
enclosingStmt(#20067,#20064)
exprContainers(#20067,#20001)
#20069=*
exprs(#20069,79,#20067,-1,"base")
hasLocation(#20069,#20021)
enclosingStmt(#20069,#20064)
exprContainers(#20069,#20001)
literals("base","base",#20069)
bind(#20069,#20061)
#20070=*
exprs(#20070,79,#20067,0,"x")
hasLocation(#20070,#20027)
enclosingStmt(#20070,#20064)
exprContainers(#20070,#20001)
literals("x","x",#20070)
#20071=@"var;{x};{#20000}"
variables(#20071,"x",#20000)
bind(#20070,#20071)
isOptionalChaining(#20067)
#20072=*
exprs(#20072,0,#20065,1,"y")
hasLocation(#20072,#20033)
enclosingStmt(#20072,#20064)
exprContainers(#20072,#20001)
literals("y","y",#20072)
#20073=*
stmts(#20073,2,#20001,2,"base?.[z].y;")
hasLocation(#20073,#20007)
stmtContainers(#20073,#20001)
#20074=*
exprs(#20074,14,#20073,0,"base?.[z].y")
#20075=@"loc,{#10000},3,1,3,11"
locations_default(#20075,#10000,3,1,3,11)
hasLocation(#20074,#20075)
enclosingStmt(#20074,#20073)
exprContainers(#20074,#20001)
#20076=*
exprs(#20076,15,#20074,0,"base?.[z]")
#20077=@"loc,{#10000},3,1,3,9"
locations_default(#20077,#10000,3,1,3,9)
hasLocation(#20076,#20077)
enclosingStmt(#20076,#20073)
exprContainers(#20076,#20001)
#20078=*
exprs(#20078,79,#20076,0,"base")
hasLocation(#20078,#20037)
enclosingStmt(#20078,#20073)
exprContainers(#20078,#20001)
literals("base","base",#20078)
bind(#20078,#20061)
#20079=*
exprs(#20079,79,#20076,1,"z")
hasLocation(#20079,#20043)
enclosingStmt(#20079,#20073)
exprContainers(#20079,#20001)
literals("z","z",#20079)
#20080=@"var;{z};{#20000}"
variables(#20080,"z",#20000)
bind(#20079,#20080)
isOptionalChaining(#20076)
#20081=*
exprs(#20081,0,#20074,1,"y")
hasLocation(#20081,#20049)
enclosingStmt(#20081,#20073)
exprContainers(#20081,#20001)
literals("y","y",#20081)
#20082=*
entry_cfg_node(#20082,#20001)
#20083=@"loc,{#10000},1,1,1,0"
locations_default(#20083,#10000,1,1,1,0)
hasLocation(#20082,#20083)
#20084=*
exit_cfg_node(#20084,#20001)
hasLocation(#20084,#20053)
successor(#20073,#20078)
successor(#20081,#20074)
successor(#20079,#20076)
successor(#20078,#20079)
successor(#20076,#20081)
successor(#20078,#20084)
successor(#20074,#20084)
successor(#20064,#20069)
successor(#20072,#20065)
successor(#20070,#20067)
successor(#20069,#20070)
successor(#20067,#20072)
successor(#20069,#20073)
successor(#20065,#20073)
successor(#20055,#20060)
successor(#20063,#20056)
successor(#20062,#20058)
successor(#20060,#20062)
successor(#20058,#20063)
successor(#20060,#20064)
successor(#20056,#20064)
successor(#20082,#20055)
numlines(#10000,3,3,0)
filetype(#10000,"typescript")

View File

@@ -4229,12 +4229,11 @@ properties(#21361,#21350,2,8,"field: T")
locations_default(#21362,#10000,55,3,55,10)
hasLocation(#21361,#21362)
#21363=*
exprs(#21363,0,#21361,0,"field")
hasLocation(#21363,#20821)
enclosingStmt(#21363,#21350)
exprContainers(#21363,#20001)
literals("field","field",#21363)
#21364=*
exprs(#21364,0,#21361,0,"field")
hasLocation(#21364,#20821)
exprContainers(#21364,#21363)
literals("field","field",#21364)
#21365=*
typeexprs(#21365,0,#21361,2,"T")
hasLocation(#21365,#20825)
@@ -4298,21 +4297,21 @@ hasLocation(#21378,#21377)
enclosingStmt(#21378,#21350)
exprContainers(#21378,#20001)
literals("constructor","constructor",#21378)
exprs(#21364,9,#21376,1,"() {}")
hasLocation(#21364,#21377)
enclosingStmt(#21364,#21350)
exprContainers(#21364,#20001)
exprs(#21363,9,#21376,1,"() {}")
hasLocation(#21363,#21377)
enclosingStmt(#21363,#21350)
exprContainers(#21363,#20001)
#21379=*
scopes(#21379,1)
scopenodes(#21364,#21379)
scopenodes(#21363,#21379)
scopenesting(#21379,#21353)
#21380=@"var;{arguments};{#21379}"
variables(#21380,"arguments",#21379)
isArgumentsObject(#21380)
#21381=*
stmts(#21381,1,#21364,-2,"{}")
stmts(#21381,1,#21363,-2,"{}")
hasLocation(#21381,#21377)
stmtContainers(#21381,#21364)
stmtContainers(#21381,#21363)
isMethod(#21376)
#21382=*
stmts(#21382,26,#20001,45,"class Fish {}")
@@ -4653,18 +4652,18 @@ successor(#21387,#21388)
successor(#21385,#21382)
successor(#21383,#21387)
successor(#21382,#21393)
successor(#21363,#21361)
successor(#21364,#21376)
successor(#21364,#21361)
successor(#21363,#21376)
#21461=*
entry_cfg_node(#21461,#21364)
entry_cfg_node(#21461,#21363)
hasLocation(#21461,#21377)
successor(#21361,#21381)
#21462=*
exit_cfg_node(#21462,#21364)
exit_cfg_node(#21462,#21363)
hasLocation(#21462,#21377)
successor(#21381,#21462)
successor(#21461,#21363)
successor(#21378,#21364)
successor(#21461,#21364)
successor(#21378,#21363)
successor(#21376,#21350)
successor(#21366,#21378)
successor(#21352,#21366)

View File

@@ -1,37 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Builtin functions and objects defined in the JavaScript standard library can be shadowed or redefined in user code.
This is confusing and makes code hard to understand, so it should be avoided.
</p>
</overview>
<recommendation>
<p>
Refactor the code to avoid shadowing or redefinition. For example, if a local variable has the same name as a standard
library builtin, it should be renamed.
</p>
</recommendation>
<example>
<p>
In the following example, the user-defined function <code>eval</code> shadows the builtin function <code>eval</code>
defined in the standard library. It could be renamed <code>evaluate</code> to avoid confusion.
</p>
<sample src="examples/BuiltinRedefined.js" />
</example>
<references>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 15. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,50 +0,0 @@
/**
* @name Builtin redefined
* @description Standard library functions can be redefined, but this should be avoided
* since it makes code hard to read and maintain.
* @kind problem
* @problem.severity recommendation
* @id js/builtin-redefinition
* @tags maintainability
* @precision medium
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
import Definitions
/**
* Holds if `id` is a redefinition of a standard library function that is considered
* acceptable since it merely introduces a local alias to the standard function of
* the same name.
*/
predicate acceptableRedefinition(Identifier id) {
// function(x, y, undefined) { ... }(23, 42)
id.getName() = "undefined" and
exists(ImmediatelyInvokedFunctionExpr iife |
id = iife.getParameter(iife.getInvocation().getNumArgument())
)
or
// Date = global.Date
exists(AssignExpr assgn |
id = assgn.getTarget() and
id.getName() = assgn.getRhs().getUnderlyingValue().(PropAccess).getPropertyName()
)
or
// var Date = global.Date
exists(VariableDeclarator decl |
id = decl.getBindingPattern() and
id.getName() = decl.getInit().getUnderlyingValue().(PropAccess).getPropertyName()
)
}
from DefiningIdentifier id, string name
where
not id.inExternsFile() and
name = id.getName() and
name
.regexpMatch("Object|Function|Array|String|Boolean|Number|Math|Date|RegExp|Error|" +
"NaN|Infinity|undefined|eval|parseInt|parseFloat|isNaN|isFinite|" +
"decodeURI|decodeURIComponent|encodeURI|encodeURIComponent") and
not acceptableRedefinition(id)
select id, "Redefinition of " + name + "."

View File

@@ -1,46 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Defining a method by assigning a closure to a property of the receiver object in the constructor
is inefficient, since a new closure is created for every instance. This wastes heap space and may
interfere with JIT compilation.
</p>
</overview>
<recommendation>
<p>
Assign the function to a property of the prototype object instead. That way, all instances share
the same closure.
</p>
</recommendation>
<example>
<p>
In the following example, constructor <code>Point</code> defines method <code>move</code> by creating
a new closure and storing it in the <code>move</code> property of each new instance. Consequently,
<code>p.move</code> and <code>q.move</code> are different methods.
</p>
<sample src="examples/InefficientMethodDefinition.js" />
<p>
It is better to instead define <code>move</code> on the prototype object <code>Point.prototype</code>
like this:
</p>
<sample src="examples/InefficientMethodDefinitionGood.js" />
</example>
<references>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain">Inheritance and the prototype chain</a>.</li>
</references>
</qhelp>

View File

@@ -1,41 +0,0 @@
/**
* @name Inefficient method definition
* @description Defining methods in the constructor (as opposed to adding them to the
* prototype object) is inefficient.
* @kind problem
* @problem.severity recommendation
* @id js/method-definition-in-constructor
* @tags efficiency
* maintainability
* @precision medium
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
import semmle.javascript.RestrictedLocations
/**
* Holds if `stmt` is of the form `this.<name> = <method>;`.
*/
predicate methodDefinition(ExprStmt stmt, string name, Function method) {
exists(AssignExpr assgn, PropAccess pacc |
assgn = stmt.getExpr() and
pacc = assgn.getLhs() and
pacc.getBase() instanceof ThisExpr and
name = pacc.getPropertyName() and
method = assgn.getRhs()
)
}
from Function ctor, ExprStmt defn, string name, Function method
where
not ctor instanceof ImmediatelyInvokedFunctionExpr and
defn = ctor.getABodyStmt() and
methodDefinition(defn, name, method) and
// if the method captures a local variable of the constructor, it cannot
// easily be moved to the constructor object
not exists(Variable v | v.getScope() = ctor.getScope() |
v.getAnAccess().getContainer().getEnclosingContainer*() = method
)
select defn.(FirstLineOf),
name + " should be added to the prototype object rather than to each instance."

View File

@@ -0,0 +1,67 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The TypeScript compiler has to choose which specific overload is called
when a method with multiple overloads is called.
The compiler will always choose the textually first overload that does
not give rise to any type errors with the arguments provided at the
function call.
</p>
<p>
This behavior can be unintuitive for programmers unfamiliar with the
type system in TypeScript, and can in some instances lead to situations
where a programmer writes an overloaded method where only the first
overload can ever be used.
</p>
</overview>
<recommendation>
<p>
Either reorder the method overloads if an overload with more type
parameters is placed before a similar overload with fewer parameters.
Alternatively, collapse multiple overloads with identical parameter types by
creating a single overload that returns a union of the return types
from the multiple overloads.
</p>
</recommendation>
<example>
<p>
In the example below, a programmer has tried to express that a method
can return multiple possible values by creating multiple overloads
with identical parameter types. However, only the first overload
will ever be selected by the TypeScript compiler.
</p>
<sample src="examples/UnreachableMethodOverloads.ts" />
<p>
The error can be fixed by merging the overloads into a single method
signature that returns a union of the previous return types.
</p>
<sample src="examples/UnreachableMethodOverloadsGood.ts" />
<p>
In the example below, an interface <code>Foo</code> declares a method
<code>create()</code> with two overloads. The only difference between
the two overloads is the type parameter <code>T</code> in the first
overload. The TypeScript compiler will always use the first overload
when <code>create()</code> is called, as a default type will be used
for the type parameter <code>T</code> if none is provided.
This default type is <code>unknown</code> in TypeScript 3.5+, and
<code>{}</code> in earlier versions.
</p>
<sample src="examples/UnreachableMethodOverloadsTypeParameters.ts" />
<p>
In this example, the error has been fixed by switching the order of the two
overloads. In this fixed version, if the <code>create()</code> method
is called with an explicit type argument the second overload will be
used, as the first overload would give rise to a type error.
</p>
<sample src="examples/UnreachableMethodOverloadsTypeParametersGood.ts" />
</example>
<references>
<li>TypeScript specification: <a href="https://github.com/microsoft/TypeScript/blob/7be7cba050799bc11c9411babd31f44c9ec087f0/doc/spec.md#4.15.1">Overload Resolution</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,140 @@
/**
* @name Unreachable method overloads
* @description Having multiple overloads with the same parameter types in TypeScript
* makes all overloads except the first one unreachable, as the compiler
* always resolves calls to the textually first matching overload.
* @kind problem
* @problem.severity warning
* @id js/unreachable-method-overloads
* @precision high
* @tags correctness
* typescript
*/
import javascript
/**
* Gets the `i`th parameter from the method signature.
*/
SimpleParameter getParameter(MethodSignature sig, int i) { result = sig.getBody().getParameter(i) }
/**
* Gets a string-representation of the type-annotation from the `i`th parameter in the method signature.
*/
string getParameterTypeAnnotation(MethodSignature sig, int i) {
result = getParameter(sig, i).getTypeAnnotation().toString()
}
/**
* Gets the other overloads for an overloaded method signature.
*/
MethodSignature getOtherMatchingSignatures(MethodSignature sig) {
signaturesMatch(result, sig) and
result != sig
}
/**
* Gets the kind of the member-declaration. Either "static" or "instance".
*/
string getKind(MemberDeclaration m) {
if m.isStatic() then result = "static" else result = "instance"
}
/**
* A call-signature that originates from a MethodSignature in the AST.
*/
private class MethodCallSig extends CallSignatureType {
string name;
MethodCallSig() {
exists(MethodSignature sig |
this = sig.getBody().getCallSignature() and
name = sig.getName()
)
}
/**
* Gets the name of any member that has this signature.
*/
string getName() {
result = name
}
}
/**
* Holds if the two call signatures could be overloads of each other and have the same parameter types.
*/
predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) {
method.getName() = other.getName() and
method.getNumOptionalParameter() = other.getNumOptionalParameter() and
method.getNumParameter() = other.getNumParameter() and
method.getNumRequiredParameter() = other.getNumRequiredParameter() and
// purposely not looking at number of type arguments.
method.getKind() = other.getKind() and
forall(int i | i in [0 .. -1 + method.getNumParameter()] |
method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations.
) and
// shared type parameters are equal.
forall(int i | i in [0 .. -1 + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] |
method.getTypeParameterBound(i) = other.getTypeParameterBound(i)
)
}
/**
* Gets which overload index the MethodSignature has among the overloads of the same name.
*/
int getOverloadIndex(MethodSignature sig) {
sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig
}
/**
* Holds if the two method signatures are overloads of each other and have the same parameter types.
*/
predicate signaturesMatch(MethodSignature method, MethodSignature other) {
// declared in the same interface/class.
method.getDeclaringType() = other.getDeclaringType() and
// same static modifier.
getKind(method) = getKind(other) and
// same name.
method.getName() = other.getName() and
// same number of parameters.
method.getBody().getNumParameter() = other.getBody().getNumParameter() and
// The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar.
forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] |
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
) and
matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature())
}
from ClassOrInterface decl, string name, MethodSignature previous, MethodSignature unreachable
where
previous = decl.getMethod(name) and
unreachable = getOtherMatchingSignatures(previous) and
// If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern.
not exists(decl.getASuperTypeDeclaration().getMethod(name)) and
not exists(ClassOrInterface sub |
decl = sub.getASuperTypeDeclaration() and
exists(sub.getMethod(name))
) and
// If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite.
// This comparison removes those cases.
unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and
// We always select the first of the overloaded methods.
not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) |
getOverloadIndex(later) < getOverloadIndex(previous)
)
select unreachable,
"This overload of " + name + "() is unreachable, the $@ overload will always be selected.", previous, "previous"

View File

@@ -0,0 +1,5 @@
interface Foo {
getParsedThing(id: string): string[];
getParsedThing(id: string): number[];
getParsedThing(id: string): object[];
}

View File

@@ -0,0 +1,3 @@
interface Foo {
getParsedThing(id: string): object[] | number[] | string[];
}

View File

@@ -0,0 +1,4 @@
interface Foo {
create<T>(a: string): MyObject<T>;
create(a: string): MyObject<any>;
}

View File

@@ -0,0 +1,4 @@
interface Foo {
create(a: string): Array<any>;
create<T>(a: string): Array<T>;
}

View File

@@ -1,45 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Avoid using <code>x % 2 === 1</code> or <code>x % 2 &gt; 0</code> to check whether a number
<code>x</code> is odd, or <code>x % 2 !== 1</code> to check whether it is even.
Such code does not work for negative numbers: for example, <code>-5 % 2</code> equals
<code>-1</code>, not <code>1</code>.
</p>
</overview>
<recommendation>
<p>
Consider using <code>x % 2 !== 0</code> to check for odd parity and <code>x % 2 === 0</code>
to check for even parity.
</p>
</recommendation>
<example>
<p>
The following code snippet does not detect -9 as an odd number because <code>-9 % 2</code>
is <code>-1</code>, not <code>1</code>.</p>
<sample src="examples/BadParityCheck.js" />
<p>
The check should be rewritten as follows:
</p>
<sample src="examples/BadParityCheckGood.js" />
</example>
<references>
<li>J. Bloch and N. Gafter, <em>Java Puzzlers: Traps, Pitfalls, and Corner Cases</em>, Puzzle 1. Addison-Wesley, 2005.</li>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 11.5.3. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,105 +0,0 @@
/**
* @name Bad parity check
* @description Ensure that parity checks take negative numbers into account.
* @kind problem
* @problem.severity recommendation
* @id js/incomplete-parity-check
* @tags reliability
* correctness
* external/cwe/cwe-480
* @precision low
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
/*
* The following predicates implement a simple analysis for identifying
* expressions that are guaranteed to only evaluate to non-negative numbers:
*
* - non-negative number literals
* - applications of (), ++, + to expressions known to be non-negative
* - references to local variables that are only assigned non-negative values,
* never decremented, and never subjected to any compound assignments except
* += where the rhs is known to be non-negative
*
* This is a greatest-fixpoint problem: if we have `x = 0`, `y = x`, `x = y`,
* we want to conclude that both `x` and `y` are non-negative. Hence we have
* to implement the analysis the other way around, as a conservative check
* for negativity.
*/
/**
* Holds if `e` is an expression that is relevant for the maybe-negative analysis.
*/
predicate relevant(Expr e) {
// base case: left operands of `%`
exists(ModExpr me | e = me.getLeftOperand())
or
// first inductive case: downward AST traversal
relevant(e.getParentExpr())
or
// second inductive case: following variable assignments
exists(Variable v | relevant(v.getAnAccess()) | e = v.getAnAssignedExpr())
}
/** Holds if `e` could evaluate to a negative number. */
predicate maybeNegative(Expr e) {
relevant(e) and
if exists(e.getIntValue())
then e.getIntValue() < 0
else
if e instanceof ParExpr
then maybeNegative(e.(ParExpr).getExpression())
else
if e instanceof IncExpr
then maybeNegative(e.(IncExpr).getOperand())
else
if e instanceof VarAccess
then maybeNegativeVar(e.(VarAccess).getVariable())
else
if e instanceof AddExpr
then maybeNegative(e.(AddExpr).getAnOperand())
else
// anything else is considered to possibly be negative
any()
}
/** Holds if `v` could be assigned a negative number. */
predicate maybeNegativeVar(Variable v) {
v.isGlobal()
or
v.isParameter()
or
// is v ever assigned a potentially negative value?
maybeNegative(v.getAnAssignedExpr())
or
// is v ever decremented?
exists(DecExpr dec | dec.getOperand().getUnderlyingReference() = v.getAnAccess())
or
// is v ever subject to a compound assignment other than +=, or to
// += with potentially negative rhs?
exists(CompoundAssignExpr assgn | assgn.getTarget() = v.getAnAccess() |
not assgn instanceof AssignAddExpr or
maybeNegative(assgn.getRhs())
)
}
from Comparison cmp, ModExpr me, int num, string parity
where
cmp.getAnOperand().stripParens() = me and
cmp.getAnOperand().getIntValue() = num and
me.getRightOperand().getIntValue() = 2 and
maybeNegative(me.getLeftOperand()) and
(
(cmp instanceof EqExpr or cmp instanceof StrictEqExpr) and
num = 1 and
parity = "oddness"
or
(cmp instanceof NEqExpr or cmp instanceof StrictNEqExpr) and
num = 1 and
parity = "evenness"
or
cmp instanceof GTExpr and num = 0 and parity = "oddness"
)
select cmp, "Test for " + parity + " does not take negative numbers into account."

View File

@@ -13,122 +13,10 @@
*/
import javascript
import DOMProperties
import semmle.javascript.frameworks.xUnit
import semmle.javascript.RestrictedLocations
import ExprHasNoEffect
import semmle.javascript.RestrictedLocations
/**
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
*
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
* attached to them.
*/
predicate isDeclaration(Expr e) {
(e instanceof VarAccess or e instanceof PropAccess) and
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
}
/**
* Holds if there exists a getter for a property called `name` anywhere in the program.
*/
predicate isGetterProperty(string name) {
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
// ... where `descriptor` defines a getter
defProp.hasPropertyAttributeWrite("get", _)
or
// ... where `descriptor` may define a getter
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
descriptor.isIncomplete(_)
or
// minimal escape analysis for the descriptor
exists(DataFlow::InvokeNode invk |
not invk = defProp and
descriptor.flowsTo(invk.getAnArgument())
)
)
)
or
// there is an object expression with a getter property `name`
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
}
/**
* A property access that may invoke a getter.
*/
class GetterPropertyAccess extends PropAccess {
override predicate isImpure() { isGetterProperty(getPropertyName()) }
}
/**
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
* `dummy` is some expression whose value is discarded, and which simply
* exists to prevent the call from being interpreted as a direct eval.
*/
predicate isIndirectEval(CallExpr c, Expr dummy) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
seq.getNumOperands() = 2
)
}
/**
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
* some expression whose value is discarded, and which simply exists
* to prevent the call from being interpreted as a method call.
*/
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1) = callee and
seq.getNumOperands() = 2
)
}
/**
* Holds if evaluating `e` has no side effects (except potentially allocating
* and initializing a new object).
*
* For calls, we do not check whether their arguments have any side effects:
* even if they do, the call itself is useless and should be flagged by this
* query.
*/
predicate noSideEffects(Expr e) {
e.isPure()
or
// `new Error(...)`, `new SyntaxError(...)`, etc.
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
f.(ExternalType).getASupertype*().getName() = "Error"
)
}
from Expr e
where
noSideEffects(e) and
inVoidContext(e) and
// disregard pure expressions wrapped in a void(...)
not e instanceof VoidExpr and
// filter out directives (unknown directives are handled by UnknownDirective.ql)
not exists(Directive d | e = d.getExpr()) and
// or about externs
not e.inExternsFile() and
// don't complain about declarations
not isDeclaration(e) and
// exclude DOM properties, which sometimes have magical auto-update properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude xUnit.js annotations
not e instanceof XUnitAnnotation and
// exclude common patterns that are most likely intentional
not isIndirectEval(_, e) and
not isReceiverSuppressingCall(_, e, _) and
// exclude anonymous function expressions as statements; these can only arise
// from a syntax error we already flag
not exists(FunctionExpr fe, ExprStmt es | fe = e |
fe = es.getExpr() and
not exists(fe.getName())
)
where hasNoEffect(e)
select e.(FirstLineOf), "This expression has no effect."

View File

@@ -3,6 +3,8 @@
*/
import javascript
import DOMProperties
import semmle.javascript.frameworks.xUnit
/**
* Holds if `e` appears in a syntactic context where its value is discarded.
@@ -37,3 +39,121 @@ predicate inVoidContext(Expr e) {
or
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
}
/**
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
*
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
* attached to them.
*/
predicate isDeclaration(Expr e) {
(e instanceof VarAccess or e instanceof PropAccess) and
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
}
/**
* Holds if there exists a getter for a property called `name` anywhere in the program.
*/
predicate isGetterProperty(string name) {
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
// ... where `descriptor` defines a getter
defProp.hasPropertyAttributeWrite("get", _)
or
// ... where `descriptor` may define a getter
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
descriptor.isIncomplete(_)
or
// minimal escape analysis for the descriptor
exists(DataFlow::InvokeNode invk |
not invk = defProp and
descriptor.flowsTo(invk.getAnArgument())
)
)
)
or
// there is an object expression with a getter property `name`
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
}
/**
* A property access that may invoke a getter.
*/
class GetterPropertyAccess extends PropAccess {
override predicate isImpure() { isGetterProperty(getPropertyName()) }
}
/**
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
* `dummy` is some expression whose value is discarded, and which simply
* exists to prevent the call from being interpreted as a direct eval.
*/
predicate isIndirectEval(CallExpr c, Expr dummy) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
seq.getNumOperands() = 2
)
}
/**
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
* some expression whose value is discarded, and which simply exists
* to prevent the call from being interpreted as a method call.
*/
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
dummy = seq.getOperand(0) and
seq.getOperand(1) = callee and
seq.getNumOperands() = 2
)
}
/**
* Holds if evaluating `e` has no side effects (except potentially allocating
* and initializing a new object).
*
* For calls, we do not check whether their arguments have any side effects:
* even if they do, the call itself is useless and should be flagged by this
* query.
*/
predicate noSideEffects(Expr e) {
e.isPure()
or
// `new Error(...)`, `new SyntaxError(...)`, etc.
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
f.(ExternalType).getASupertype*().getName() = "Error"
)
}
/**
* Holds if the expression `e` should be reported as having no effect.
*/
predicate hasNoEffect(Expr e) {
noSideEffects(e) and
inVoidContext(e) and
// disregard pure expressions wrapped in a void(...)
not e instanceof VoidExpr and
// filter out directives (unknown directives are handled by UnknownDirective.ql)
not exists(Directive d | e = d.getExpr()) and
// or about externs
not e.inExternsFile() and
// don't complain about declarations
not isDeclaration(e) and
// exclude DOM properties, which sometimes have magical auto-update properties
not isDOMProperty(e.(PropAccess).getPropertyName()) and
// exclude xUnit.js annotations
not e instanceof XUnitAnnotation and
// exclude common patterns that are most likely intentional
not isIndirectEval(_, e) and
not isReceiverSuppressingCall(_, e, _) and
// exclude anonymous function expressions as statements; these can only arise
// from a syntax error we already flag
not exists(FunctionExpr fe, ExprStmt es | fe = e |
fe = es.getExpr() and
not exists(fe.getName())
)
}

View File

@@ -1,56 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
In JavaScript, properties of objects do not have to be declared and can be dynamically added
and removed at runtime. Thus, if a property name is misspelled, this is not detected by the
compiler, and may lead to an error at runtime. The same problem occurs with misspelled
global variables.
</p>
<p>
This rule flags property names and global variables that are mentioned only once, but where
a different capitalization of the same name is used in multiple other places, suggesting a typo.
</p>
</overview>
<recommendation>
<p>
Check whether the name has been misspelled. If the name is correct, consider using
a <a href="http://www.jslint.com/help.html#properties">JSLint-style</a>
<code>/*property ...*/</code> directive to document the existence of this property,
or provide an externs file declaring the property.
</p>
</recommendation>
<example>
<p>
The following code snippet contains two uses of the <code>log</code> method, but only
one use of the <code>Log</code> method. This suggests that <code>Log</code> may be a typo
for <code>log</code>.
</p>
<sample src="examples/HapaxLegomenon.js" />
<p>
If the use of <code>Log</code> is, in fact, a typo, it should be corrected. Otherwise, a
<code>properties</code> directive can be introduced to document the fact that both
<code>log</code> and <code>Log</code> properties exist:
</p>
<sample src="examples/HapaxLegomenonGood.js" />
</example>
<references>
<li>JSLint: <a href="http://www.jslint.com/help.html#properties">Property</a>.</li>
<li>Google Closure Tools: <a href="https://developers.google.com/closure/compiler/docs/api-tutorial3?csw=1#externs">Declaring externs</a>.</li>
</references>
</qhelp>

View File

@@ -1,91 +0,0 @@
/**
* @name Potentially misspelled property or variable name
* @description A property or variable is only mentioned once, but there is one with the same name
* in different capitalization that is mentioned more than once, suggesting that this
* may be a typo.
* @kind problem
* @problem.severity warning
* @id js/wrong-capitalization
* @tags reliability
* @precision low
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
/** Gets the number of identifiers and string literals that refer to `name`. */
int countOccurrences(string name) {
(
exists(PropAccess pacc | name = pacc.getPropertyName()) or
exists(VarAccess acc | name = acc.getName())
) and
result = strictcount(Expr id |
id.(Identifier).getName() = name
or
// count string literals as well to capture meta-programming
id.getStringValue() = name
)
}
/**
* An access to an undeclared variable or property that is only referenced
* once in the entire program.
*/
abstract class Hapax extends @expr {
/** Gets the name of the accessed variable or property. */
abstract string getName();
/** Gets a textual representation of this element. */
string toString() { result = this.(Expr).toString() }
}
/**
* An access to a property that is covered neither by a JSLint property declaration
* nor by an externs declaration, and that is only mentioned once in the entire program.
*/
class UndeclaredPropertyAccess extends Hapax, @dotexpr {
UndeclaredPropertyAccess() {
exists(string name | name = this.(DotExpr).getPropertyName() |
countOccurrences(name) = 1 and
not exists(JSLintProperties jslpd | jslpd.appliesTo(this) and jslpd.getAProperty() = name) and
not exists(ExternalMemberDecl emd | emd.getProperty() = this)
)
}
override string getName() { result = this.(DotExpr).getPropertyName() }
}
/**
* An access to a global variable that is neither declared nor covered by a linter
* directive, and that is only mentioned once in the entire program.
*/
class UndeclaredGlobal extends Hapax, @varaccess {
UndeclaredGlobal() {
exists(GlobalVariable gv, string name | this = gv.getAnAccess() and name = gv.getName() |
countOccurrences(name) = 1 and
not exists(Linting::GlobalDeclaration glob | glob.declaresGlobalForAccess(this)) and
not exists(gv.getADeclaration())
)
}
override string getName() { result = this.(VarAccess).getName() }
}
/**
* Gets the number of occurrences of `m`, which is the same as `hapax`
* except for capitalization, ensuring that it occurs at least twice.
*/
int candidateSpellingCount(Hapax hapax, string m) {
exists(string n | n = hapax.getName() |
m.toLowerCase() = n.toLowerCase() and
m != n and
result = countOccurrences(m) and
result > 1
)
}
from Hapax hapax, string n, string m
where
n = hapax.getName() and
candidateSpellingCount(hapax, m) = max(candidateSpellingCount(hapax, _))
select hapax.(Expr), "'" + n + "' is mentioned only once; it may be a typo for '" + m + "'."

View File

@@ -22,14 +22,51 @@ Expr leftChild(Expr e) {
result = e.(AddExpr).getLeftOperand()
}
class LiteralOrTemplate extends Expr {
LiteralOrTemplate() {
this instanceof TemplateLiteral or
this instanceof Literal
predicate isInConcat(Expr e) {
exists(ParExpr par | isInConcat(par) and par.getExpression() = e)
or
exists(AddExpr a | a.getAnOperand() = e)
}
class ConcatenationLiteral extends Expr {
ConcatenationLiteral() {
(
this instanceof TemplateLiteral
or
this instanceof Literal
)
and isInConcat(this)
}
}
from AddExpr e, LiteralOrTemplate l, LiteralOrTemplate r, string word
Expr getConcatChild(Expr e) {
result = rightChild(e) or
result = leftChild(e)
}
Expr getConcatParent(Expr e) {
e = getConcatChild(result)
}
predicate isWordLike(ConcatenationLiteral lit) {
lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*")
}
class ConcatRoot extends AddExpr {
ConcatRoot() {
not isInConcat(this)
}
}
ConcatRoot getAddRoot(AddExpr e) {
result = getConcatParent*(e)
}
predicate hasWordLikeFragment(AddExpr e) {
isWordLike(getConcatChild*(getAddRoot(e)))
}
from AddExpr e, ConcatenationLiteral l, ConcatenationLiteral r, string word
where
// l and r are appended together
l = rightChild*(e.getLeftOperand()) and
@@ -41,5 +78,8 @@ where
// needed, and intra-identifier punctuation in, for example, a qualified name.
word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and
r.getStringValue().regexpMatch("[a-zA-Z].*") and
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]")
not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") and
// There must be a constant-string in the concatenation that looks like a word.
hasWordLikeFragment(e)
select l, "This string appears to be missing a space after '" + word + "'."

View File

@@ -13,6 +13,8 @@ import javascript
from Directive d
where
not d instanceof KnownDirective and
// ignore ":" pseudo-directive sometimes seen in dual-use shell/node.js scripts
not d.getExpr().getStringValue() = ":" and
// but exclude attribute top-levels: `<a href="javascript:'some-attribute-string'">`
not d.getParent() instanceof CodeInAttribute
select d, "Unknown directive: '" + truncate(d.getDirectiveText(), 20, " ... (truncated)") + "'."

View File

@@ -1,38 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Non-standard JSDoc tags are undesirable, since JSDoc-processing tools will either ignore
them or treat them as plain text.
</p>
</overview>
<recommendation>
<p>
Check whether the tag name is misspelled, or consult the JSDoc documentation to find out
what standard tags are available.
</p>
</recommendation>
<example>
<p>
In the following example, the constructor <code>Message</code> has a JSDoc comment describing
its parameters, but the second <code>@param</code> tag has been misspelled as
<code>@parma</code>.
</p>
<sample src="examples/UnknownTagType.js" />
</example>
<references>
<li>Use JSDoc: <a href="http://usejsdoc.org/index.html">Tag Dictionary</a>.</li>
</references>
</qhelp>

View File

@@ -1,139 +0,0 @@
/**
* @name Unknown JSDoc tag
* @description A JSDoc tag with a non-standard tag type will either be ignored or treated as plain
* text by JSDoc-processing tools.
* @kind problem
* @problem.severity recommendation
* @id js/jsdoc/unknown-tag-type
* @tags maintainability
* readability
* documentation
* @precision low
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
/** Holds if `tp` is a standard tag type. */
predicate knownTagType(string tp) {
tp = "abstract" or
tp = "access" or
tp = "alias" or
tp = "api" or
tp = "arg" or
tp = "argument" or
tp = "augments" or
tp = "author" or
tp = "borrows" or
tp = "bug" or
tp = "callback" or
tp = "category" or
tp = "class" or
tp = "classdesc" or
tp = "const" or
tp = "constant" or
tp = "constructor" or
tp = "constructs" or
tp = "copyright" or
tp = "default" or
tp = "defaultvalue" or
tp = "define" or
tp = "depend" or
tp = "depends" or
tp = "deprecated" or
tp = "desc" or
tp = "description" or
tp = "dict" or
tp = "emits" or
tp = "enum" or
tp = "event" or
tp = "example" or
tp = "exception" or
tp = "export" or
tp = "exports" or
tp = "expose" or
tp = "extends" or
tp = "external" or
tp = "externs" or
tp = "field" or
tp = "file" or
tp = "fileoverview" or
tp = "final" or
tp = "fires" or
tp = "flow" or
tp = "func" or
tp = "function" or
tp = "global" or
tp = "host" or
tp = "ignore" or
tp = "implements" or
tp = "implicitCast" or
tp = "inheritDoc" or
tp = "inner" or
tp = "interface" or
tp = "internal" or
tp = "instance" or
tp = "kind" or
tp = "lends" or
tp = "license" or
tp = "link" or
tp = "member" or
tp = "memberof" or
tp = "memberOf" or
tp = "method" or
tp = "mixes" or
tp = "mixin" or
tp = "modifies" or
tp = "module" or
tp = "modName" or
tp = "mods" or
tp = "name" or
tp = "namespace" or
tp = "ngInject" or
tp = "noalias" or
tp = "nocompile" or
tp = "nosideeffects" or
tp = "note" or
tp = "override" or
tp = "overview" or
tp = "owner" or
tp = "package" or
tp = "param" or
tp = "preserve" or
tp = "preserveTry" or
tp = "private" or
tp = "prop" or
tp = "property" or
tp = "protected" or
tp = "providesModule" or
tp = "public" or
tp = "readonly" or
tp = "requires" or
tp = "returns" or
tp = "return" or
tp = "see" or
tp = "since" or
tp = "static" or
tp = "struct" or
tp = "summary" or
tp = "supported" or
tp = "suppress" or
tp = "template" or
tp = "this" or
tp = "throws" or
tp = "todo" or
tp = "tutorial" or
tp = "type" or
tp = "typedef" or
tp = "var" or
tp = "variation" or
tp = "version" or
tp = "virtual" or
tp = "visibility" or
tp = "wizaction" or
tp = "wizmodule"
}
from JSDocTag tag
where not knownTagType(tag.getTitle())
select tag, "Unknown tag type '" + tag.getTitle() + "'."

View File

@@ -1,26 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
JSLint directives must not start with a space. For example, <code>/* global window*/</code>
is not a valid directive, and will not be recognized by JSLint.
</p>
</overview>
<recommendation>
<p>
Remove the space: <code>/*global window*/</code>.
</p>
</recommendation>
<references>
<li>JSLint: <a href="http://www.jslint.com/help.html">JSLint Help</a>.</li>
</references>
</qhelp>

View File

@@ -1,21 +0,0 @@
/**
* @name Invalid JSLint directive
* @description A JSLint directive that has whitespace characters before the
* directive name is not recognized by JSLint.
* @kind problem
* @problem.severity recommendation
* @id js/jslint/invalid-directive
* @tags maintainability
* @precision medium
* @deprecated JSLint is rarely used any more. Deprecated since 1.17.
*/
import javascript
from SlashStarComment c
where
// use possessive quantifiers '*+' and '++' to avoid backtracking
c
.getText()
.regexpMatch("\\s+(global|properties|property|jslint)\\s(\\s*+[a-zA-Z$_][a-zA-Z0-9$_]*+(\\s*+:\\s*+\\w++)?\\s*+,?)++\\s*")
select c, "JSLint directives must not have whitespace characters before the directive name."

View File

@@ -1,49 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
JSLint directives must consist of a comma-separated list of flags, where each flag
can optionally be followed by a colon and a value. The value may either be a number
or a Boolean (that is, 'true' or 'false'). Directives must not contain other
characters such as '*', which some editors may automatically insert after every line
break when editing a block comment.
</p>
</overview>
<recommendation>
<p>
Insert commas where necessary and remove stray characters.
</p>
</recommendation>
<example>
<p>
For example, <code>/*jslint nomen:true vars:true*/</code> is not a well-formed
JSLint directive; it should be replaced by <code>/*jslint nomen:true, vars:true*/</code>.
</p>
<p>
This is another example of a malformed JSLint directive:
</p>
<sample src="examples/MalformedJSLintDirective.js" />
<p>
It should be fixed as follows:
</p>
<sample src="examples/MalformedJSLintDirectiveGood.js" />
</example>
<references>
<li>JSLint: <a href="http://www.jslint.com/help.html">JSLint Help</a>.</li>
</references>
</qhelp>

View File

@@ -1,26 +0,0 @@
/**
* @name Malformed JSLint directive
* @description A malformed JSLint directive will be rejected by JSLint, and may be either
* rejected or ignored by other tools.
* @kind problem
* @problem.severity recommendation
* @id js/jslint/malformed-directive
* @tags maintainability
* @precision medium
* @deprecated JSLint is rarely used any more. Deprecated since 1.17.
*/
import javascript
from JSLintDirective dir, string flag, string flags, string directive
where
// a flag, optionally followed by a colon and a value, where the value may be
// a Boolean or a number
flag = "[a-zA-Z$_][a-zA-Z0-9$_]*(\\s*:\\s*(true|false|\\d+))?" and
// a non-empty, comma-separated list of flags
flags = "(" + flag + "\\s*,\\s*)*" + flag and
// a word (which is the directive's name), followed by a possibly empty list of flags
// note that there may be trailing whitespace, but no leading whitespace
directive = "\\s*\\w+\\s+(" + flags + ")?\\s*" and
not dir.getText().regexpMatch(directive)
select dir, "Malformed JSLint directive."

View File

@@ -1,42 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
HTML comments are a technique for hiding JavaScript code from browsers that do not interpret <code>script</code>
tags. Since all popular browsers have supported <code>script</code> tags for many years, this precaution is
not needed any more.
</p>
</overview>
<recommendation>
<p>
Remove all HTML comments.
</p>
</recommendation>
<example>
<p>
The following code block uses HTML comments to hide the <code>script</code> block from ancient browsers.
</p>
<sample src="examples/HTMLComments.js" />
<p>
Since such browsers are no longer widely used, the comments should be removed:
</p>
<sample src="examples/HTMLCommentsGood.js" />
</example>
<references>
<li>JavaScript Toolbox: <a href="http://www.javascripttoolbox.com/bestpractices/#comments">JavaScript Best Practices</a>.</li>
</references>
</qhelp>

View File

@@ -1,18 +0,0 @@
/**
* @name Use of HTML comments
* @description HTML-style comments are not a standard ECMAScript feature and should be avoided.
* @kind problem
* @problem.severity recommendation
* @id js/html-comment
* @tags maintainability
* language-features
* external/cwe/cwe-758
* @precision low
* @deprecated HTML comments are recognized in the standard as an additional feature supported by
* web browsers. Deprecated since 1.17.
*/
import javascript
from HtmlLineComment c
select c, "Do not use HTML comments."

View File

@@ -61,5 +61,7 @@ where
forex(DataFlow::InvokeNode cs2, Function otherCallee |
cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() |
illegalInvocation(cs, otherCallee, _, _)
)
) and
// require that all callees are known
not cs.isIncomplete()
select cs, "Illegal invocation of $@ " + how + ".", callee, calleeDesc

View File

@@ -1,40 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Multi-line string literals are not supported on all platforms, and thus should be avoided.
</p>
</overview>
<recommendation>
<p>
Replace multi-line string literals by multiple strings concatenated with the <code>+</code> operator.
</p>
</recommendation>
<example>
<p>
The following example contains a string literal spanning three lines:
</p>
<sample src="examples/MultilineStringLiteral.js" />
<p>
It should be rewritten like this:
</p>
<sample src="examples/MultilineStringLiteralGood.js" />
</example>
<references>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 7.8.4. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,19 +0,0 @@
/**
* @name Multi-line string literal
* @description Multi-line string literals are non-standard and hard to read, and should be avoided.
* @kind problem
* @problem.severity recommendation
* @id js/multi-line-string
* @tags maintainability
* external/cwe/cwe-758
* @precision low
* @deprecated Multi-line string literals are now a standard language feature. Deprecated since 1.17.
*/
import javascript
from StringLiteral sl, Location l
where
l = sl.getLocation() and
l.getStartLine() != l.getEndLine()
select sl, "Avoid multi-line string literals."

View File

@@ -1,40 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Integer literals starting with the digit <code>0</code> may be interpreted as octal numbers by some platforms
but not others, and thus should be avoided. This does not make a difference for the literal <code>0</code>
itself.
</p>
</overview>
<recommendation>
<p>
If the literal was meant to be octal, convert it to a decimal or hexadecimal number. Otherwise, remove
the leading zero.
</p>
</recommendation>
<example>
<p>
The following example uses the literal <code>012</code>, which some platforms will interpret as an octal
encoding of the decimal number <code>10</code>, while others will interpret it as the decimal number
<code>12</code>. Depending on the desired interpretation, it should be replaced with either <code>10</code>
or <code>12</code>.
</p>
<sample src="examples/OctalLiteral.js" />
</example>
<references>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Annex B.1.1. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,17 +0,0 @@
/**
* @name Octal literal
* @description Octal numeric literals are a platform-specific extension and should not be used.
* @kind problem
* @problem.severity recommendation
* @id js/octal-literal
* @tags portability
* external/cwe/cwe-758
* @precision low
* @deprecated This query is prone to false positives. Deprecated since 1.17.
*/
import javascript
from NumberLiteral nl
where nl.getRawValue().regexpMatch("0\\d+")
select nl, "Do not use octal literals."

View File

@@ -1,37 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The ECMAScript standard defines a list of future keywords that should not be used as identifiers.
While they may be accepted by current implementations, they may no longer be supported in the future,
so it is best not to rely on them.
</p>
</overview>
<recommendation>
<p>
Rename the identifier in question.
</p>
</recommendation>
<example>
<p>
In the following code snippet, <code>package</code> is used as a variable name. Since <code>package</code>
is a future reserved word, the variable should be renamed, for instance to <code>pkg</code>.
</p>
<sample src="examples/ReservedWords.js" />
</example>
<references>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Section 7.6.1.2. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,24 +0,0 @@
/**
* @name Reserved word used as variable name
* @description Future reserved words should not be used as variable names.
* @kind problem
* @problem.severity recommendation
* @id js/use-of-reserved-word
* @tags maintainability
* language-features
* @precision very-high
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
*/
import javascript
from Identifier id
where
id
.getName()
.regexpMatch("class|const|enum|export|extends|import|super|implements|interface|let|package|private|protected|public|static|yield") and
not exists(DotExpr de | id = de.getProperty()) and
not exists(Property prop | id = prop.getNameExpr()) and
// exclude JSX attribute names
not exists(JSXElement e | id = e.getAnAttribute().getNameExpr())
select id, "Identifier name is a reserved word."

View File

@@ -1,37 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
The ECMAScript standard allows trailing commas in array and object literals which are ignored. However,
older versions of Internet Explorer do not recognize this syntax. Moreover, it can lead to confusion
when used in array literals, since spurious commas other than the last one are not ignored but give rise
to additional undefined array elements. For these reasons, trailing commas should always be avoided.
</p>
</overview>
<recommendation>
<p>
Remove the trailing comma.
</p>
</recommendation>
<example>
<p>
The following code snippet shows an object literal with a trailing comma, which should be removed.
</p>
<sample src="examples/TrailingComma.js" />
</example>
<references>
<li>Ecma International, <i>ECMAScript Language Definition</i>, 5.1 Edition, Sections 11.1.4 and 11.1.5. ECMA, 2011.</li>
</references>
</qhelp>

View File

@@ -1,37 +0,0 @@
/**
* @name Trailing comma in array or object expressions
* @description Trailing commas in array and object expressions are interpreted differently
* by different browsers and should be avoided.
* @kind problem
* @problem.severity recommendation
* @id js/trailing-comma-in-array-or-object
* @tags portability
* external/cwe/cwe-758
* @precision low
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
*/
import javascript
/** An array or object expression. */
class ArrayOrObjectExpr extends Expr {
ArrayOrObjectExpr() {
this instanceof ArrayExpr or
this instanceof ObjectExpr
}
/** Holds if this array or object expression has a trailing comma. */
predicate hasTrailingComma() {
this.(ArrayExpr).hasTrailingComma() or
this.(ObjectExpr).hasTrailingComma()
}
/** Gets a short description of this expression. */
string getShortName() {
if this instanceof ArrayExpr then result = "array expression" else result = "object expression"
}
}
from ArrayOrObjectExpr e
where e.hasTrailingComma()
select e.getLastToken().getPreviousToken(), "Trailing comma in " + e.getShortName() + "."

View File

@@ -11,6 +11,7 @@
*/
import javascript
import semmle.javascript.CharacterEscapes
/**
* Holds if `pattern` is a regular expression pattern for URLs with a host matched by `hostPart`,
@@ -40,7 +41,9 @@ where
)
) and
// ignore patterns with capture groups after the TLD
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*")
not pattern.regexpMatch("(?i).*[.](" + RegExpPatterns::commonTLD() + ").*[(][?]:.*[)].*") and
// avoid double reporting
not CharacterEscapes::hasALikelyRegExpPatternMistake(re)
select re,
"This " + kind + " has an unescaped '.' before '" + hostPart +
"', so it might match more hosts than expected.", aux, "here"

View File

@@ -0,0 +1,86 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
When a character in a string literal or regular expression
literal is preceded by a backslash, it is interpreted as part of an
escape sequence. For example, the escape sequence <code>\n</code> in a
string literal corresponds to a single <code>newline</code> character,
and not the <code>\</code> and <code>n</code> characters.
However, not all characters change meaning when used in an
escape sequence. In this case, the backslash just makes the character
appear to mean something else, and the backslash actually has no
effect. For example, the escape sequence <code>\k</code> in a string
literal just means <code>k</code>.
Such superfluous escape sequences are usually benign, and
do not change the behavior of the program.
</p>
<p>
The set of characters that change meaning when in escape
sequences is different for regular expression literals and string
literals.
This can be problematic when a regular expression literal
is turned into a regular expression that is built from one or more
string literals. The problem occurs when a regular expression escape
sequence loses its special meaning in a string literal.
</p>
</overview>
<recommendation>
<p>
Ensure that the right amount of backslashes is used when
escaping characters in strings, template literals and regular
expressions.
Pay special attention to the number of backslashes when
rewriting a regular expression as a string literal.
</p>
</recommendation>
<example>
<p>
The following example code checks that a string is
<code>"my-marker"</code>, possibly surrounded by white space:
</p>
<sample src="examples/UselessRegExpCharacterEscape_bad_1.js"/>
<p>
However, the check does not work properly for white space
as the two <code>\s</code> occurrences are semantically equivalent to
just <code>s</code>, meaning that the check will succeed for strings
like <code>"smy-markers"</code> instead of <code>" my-marker
"</code>.
Address these shortcomings by either using a regular
expression literal (<code>/(^\s*)my-marker(\s*$)/</code>), or by
adding extra backslashes
(<code>'(^\\s*)my-marker(\\s*$)'</code>).
</p>
</example>
<references>
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping">Regular expression escape notation</a></li>
<li>MDN: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Escape_notation">String escape notation</a></li>
</references>
</qhelp>

View File

@@ -0,0 +1,156 @@
/**
* @name Useless regular-expression character escape
* @description Prepending a backslash to an ordinary character in a string
* does not have any effect, and may make regular expressions constructed from this string
* behave unexpectedly.
* @kind problem
* @problem.severity error
* @precision high
* @id js/useless-regexp-character-escape
* @tags correctness
* security
* external/cwe/cwe-20
*/
import javascript
import semmle.javascript.CharacterEscapes::CharacterEscapes
newtype TRegExpPatternMistake =
/**
* A character escape mistake in regular expression string `src`
* for the character `char` at `index` in `rawStringNode`, explained
* by `mistake`.
*/
TIdentityEscapeInStringMistake(
RegExpPatternSource src, string char, string mistake, ASTNode rawStringNode, int index
) {
char = getALikelyRegExpPatternMistake(src, mistake, rawStringNode, index)
} or
/**
* A backslash-escaped 'b' at `index` of `rawStringNode` in the
* regular expression string `src`, indicating intent to use the
* word-boundary assertion '\b'.
*/
TBackspaceInStringMistake(RegExpPatternSource src, ASTNode rawStringNode, int index) {
exists(string raw, string cooked |
exists(StringLiteral lit | lit = rawStringNode |
rawStringNode = src.asExpr() and
raw = lit.getRawValue() and
cooked = lit.getStringValue()
)
or
exists(TemplateElement elem | elem = rawStringNode |
rawStringNode = src.asExpr().(TemplateLiteral).getAnElement() and
raw = elem.getRawValue() and
cooked = elem.getStringValue()
)
|
"b" = getAnEscapedCharacter(raw, index) and
// except if the string is exactly \b
cooked.length() != 1
)
}
/**
* A character escape mistake in a regular expression string.
*
* Implementation note: the main purpose of this class is to associate an
* exact character location with an alert message, in the name of
* user-friendly alerts. The implementation can be simplified
* significantly by only using the enclosing string location as the alert
* location.
*/
class RegExpPatternMistake extends TRegExpPatternMistake {
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://help.semmle.com/QL/learn-ql/ql/locations.html).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
exists(int srcStartcolumn, int srcEndcolumn, int index |
index = getIndex() and
getRawStringNode()
.getLocation()
.hasLocationInfo(filepath, startline, srcStartcolumn, endline, srcEndcolumn)
|
(
if startline = endline
then startcolumn = srcStartcolumn + index - 1 and endcolumn = srcStartcolumn + index
else (
startcolumn = srcStartcolumn and endcolumn = srcEndcolumn
)
)
)
}
/** Gets a textual representation of this element. */
string toString() { result = getMessage() }
abstract ASTNode getRawStringNode();
abstract RegExpPatternSource getSrc();
abstract int getIndex();
abstract string getMessage();
}
/**
* An identity-escaped character that indicates programmer intent to
* do something special in a regular expression.
*/
class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscapeInStringMistake {
RegExpPatternSource src;
string char;
string mistake;
int index;
ASTNode rawStringNode;
IdentityEscapeInStringMistake() {
this = TIdentityEscapeInStringMistake(src, char, mistake, rawStringNode, index)
}
override string getMessage() {
result = "'\\" + char + "' is equivalent to just '" + char + "', so the sequence " + mistake
}
override int getIndex() { result = index }
override RegExpPatternSource getSrc() { result = src }
override ASTNode getRawStringNode() { result = rawStringNode }
}
/**
* A backspace as '\b' in a regular expression string, indicating
* programmer intent to use the word-boundary assertion '\b'.
*/
class BackspaceInStringMistake extends RegExpPatternMistake, TBackspaceInStringMistake {
RegExpPatternSource src;
int index;
ASTNode rawStringNode;
BackspaceInStringMistake() { this = TBackspaceInStringMistake(src, rawStringNode, index) }
override string getMessage() { result = "'\\b' is a backspace, and not a word-boundary assertion" }
override int getIndex() { result = index }
override RegExpPatternSource getSrc() { result = src }
override ASTNode getRawStringNode() { result = rawStringNode }
}
from RegExpPatternMistake mistake
select mistake, "The escape sequence " + mistake.getMessage() + " when it is used in a $@.",
mistake.getSrc().getAParse(), "regular expression"

View File

@@ -0,0 +1,2 @@
let regex = new RegExp('(^\s*)my-marker(\s*$)'),
isMyMarkerText = regex.test(text);

View File

@@ -0,0 +1,103 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Dynamically constructing a shell command with values from the
local environment, such as file paths, may inadvertently
change the meaning of the shell command.
Such changes can occur when an environment value contains
characters that the shell interprets in a special way, for instance
quotes and spaces.
This can result in the shell command misbehaving, or even
allowing a malicious user to execute arbitrary commands on the system.
</p>
</overview>
<recommendation>
<p>
If possible, use hard-coded string literals to specify the
shell command to run, and provide the dynamic arguments to the shell
command separately to avoid interpretation by the shell.
</p>
<p>
Alternatively, if the shell command must be constructed
dynamically, then add code to ensure that special characters in
environment values do not alter the shell command unexpectedly.
</p>
</recommendation>
<example>
<p>
The following example shows a dynamically constructed shell
command that recursively removes a temporary directory that is located
next to the currently executing JavaScript file. Such utilities are
often found in custom build scripts.
</p>
<sample src="examples/shell-command-injection-from-environment.js" />
<p>
The shell command will, however, fail to work as intended if the
absolute path of the script's directory contains spaces. In that
case, the shell command will interpret the absolute path as multiple
paths, instead of a single path.
</p>
<p>
For instance, if the absolute path of
the temporary directory is <code>/home/username/important
project/temp</code>, then the shell command will recursively delete
<code>/home/username/important</code> and <code>project/temp</code>,
where the latter path gets resolved relative to the working directory
of the JavaScript process.
</p>
<p>
Even worse, although less likely, a malicious user could
provide the path <code>/home/username/; cat /etc/passwd #/important
project/temp</code> in order to execute the command <code>cat
/etc/passwd</code>.
</p>
<p>
To avoid such potentially catastrophic behaviors, provide the
directory as an argument that does not get interpreted by a
shell:
</p>
<sample src="examples/shell-command-injection-from-environment_fixed.js" />
</example>
<references>
<li>
OWASP:
<a href="https://www.owasp.org/index.php/Command_Injection">Command Injection</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,29 @@
/**
* @name Shell command built from environment values
* @description Building a shell command string with values from the enclosing
* environment may cause subtle bugs or vulnerabilities.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id js/shell-command-injection-from-environment
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironment::ShellCommandInjectionFromEnvironment
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
Source sourceNode
where
sourceNode = source.getNode() and
cfg.hasFlowPath(source, sink) and
if cfg.isSinkWithHighlight(sink.getNode(), _)
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
else highlight = sink.getNode()
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
sourceNode.getSourceType()

View File

@@ -0,0 +1,6 @@
var cp = require("child_process"),
path = require("path");
function cleanupTemp() {
let cmd = "rm -rf " + path.join(__dirname, "temp");
cp.execSync(cmd); // BAD
}

View File

@@ -0,0 +1,7 @@
var cp = require("child_process"),
path = require("path");
function cleanupTemp() {
let cmd = "rm",
args = ["-rf", path.join(__dirname, "temp")];
cp.execFileSync(cmd, args); // GOOD
}

View File

@@ -10,8 +10,8 @@ attacks such as cross-site scripting. One particular example of this is HTML ent
where HTML special characters are replaced by HTML character entities to prevent them from being
interpreted as HTML markup. For example, the less-than character is encoded as <code>&amp;lt;</code>
and the double-quote character as <code>&amp;quot;</code>.
Other examples include backslash-escaping for including untrusted data in string literals and
percent-encoding for URI components.
Other examples include backslash escaping or JSON encoding for including untrusted data in string
literals, and percent-encoding for URI components.
</p>
<p>
The reverse process of replacing escape sequences with the characters they represent is known as

View File

@@ -46,7 +46,12 @@ string getStringValue(RegExpLiteral rl) {
*/
DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
result = nd.getAPredecessor() and
not nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition() instanceof SsaPhiNode
not exists(SsaDefinition ssa |
ssa = nd.(DataFlow::SsaDefinitionNode).getSsaVariable().getDefinition()
|
ssa instanceof SsaPhiNode or
ssa instanceof SsaVariableCapture
)
}
/**
@@ -54,38 +59,31 @@ DataFlow::Node getASimplePredecessor(DataFlow::Node nd) {
* into a form described by regular expression `regex`.
*/
predicate escapingScheme(string metachar, string regex) {
metachar = "&" and regex = "&.*;"
metachar = "&" and regex = "&.+;"
or
metachar = "%" and regex = "%.*"
metachar = "%" and regex = "%.+"
or
metachar = "\\" and regex = "\\\\.*"
metachar = "\\" and regex = "\\\\.+"
}
/**
* A call to `String.prototype.replace` that replaces all instances of a pattern.
* A method call that performs string replacement.
*/
class Replacement extends DataFlow::Node {
RegExpLiteral pattern;
Replacement() {
exists(DataFlow::MethodCallNode mcn | this = mcn |
mcn.getMethodName() = "replace" and
pattern.flow().(DataFlow::SourceNode).flowsTo(mcn.getArgument(0)) and
mcn.getNumArgument() = 2 and
pattern.isGlobal()
)
}
abstract class Replacement extends DataFlow::Node {
/**
* Holds if this replacement replaces the string `input` with `output`.
*/
predicate replaces(string input, string output) {
exists(DataFlow::MethodCallNode mcn |
mcn = this and
input = getStringValue(pattern) and
output = mcn.getArgument(1).getStringValue()
)
}
abstract predicate replaces(string input, string output);
/**
* Gets the input of this replacement.
*/
abstract DataFlow::Node getInput();
/**
* Gets the output of this replacement.
*/
abstract DataFlow::SourceNode getOutput();
/**
* Holds if this replacement escapes `char` using `metachar`.
@@ -118,9 +116,12 @@ class Replacement extends DataFlow::Node {
/**
* Gets the previous replacement in this chain of replacements.
*/
Replacement getPreviousReplacement() {
result = getASimplePredecessor*(this.(DataFlow::MethodCallNode).getReceiver())
}
Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) }
/**
* Gets the next replacement in this chain of replacements.
*/
Replacement getNextReplacement() { this = result.getPreviousReplacement() }
/**
* Gets an earlier replacement in this chain of replacements that
@@ -130,7 +131,9 @@ class Replacement extends DataFlow::Node {
exists(Replacement pred | pred = this.getPreviousReplacement() |
if pred.escapes(_, metachar)
then result = pred
else result = pred.getAnEarlierEscaping(metachar)
else (
not pred.unescapes(metachar, _) and result = pred.getAnEarlierEscaping(metachar)
)
)
}
@@ -142,11 +145,100 @@ class Replacement extends DataFlow::Node {
exists(Replacement succ | this = succ.getPreviousReplacement() |
if succ.unescapes(metachar, _)
then result = succ
else result = succ.getALaterUnescaping(metachar)
else (
not succ.escapes(_, metachar) and result = succ.getALaterUnescaping(metachar)
)
)
}
}
/**
* A call to `String.prototype.replace` that replaces all instances of a pattern.
*/
class GlobalStringReplacement extends Replacement, DataFlow::MethodCallNode {
RegExpLiteral pattern;
GlobalStringReplacement() {
this.getMethodName() = "replace" and
pattern.flow().(DataFlow::SourceNode).flowsTo(this.getArgument(0)) and
this.getNumArgument() = 2 and
pattern.isGlobal()
}
override predicate replaces(string input, string output) {
input = getStringValue(pattern) and
output = this.getArgument(1).getStringValue()
or
exists(DataFlow::FunctionNode replacer, DataFlow::PropRead pr, DataFlow::ObjectLiteralNode map |
replacer = getCallback(1) and
replacer.getParameter(0).flowsToExpr(pr.getPropertyNameExpr()) and
pr = map.getAPropertyRead() and
pr.flowsTo(replacer.getAReturn()) and
map.asExpr().(ObjectExpr).getPropertyByName(input).getInit().getStringValue() = output
)
}
override DataFlow::Node getInput() { result = this.getReceiver() }
override DataFlow::SourceNode getOutput() { result = this }
}
/**
* A call to `JSON.stringify`, viewed as a string replacement.
*/
class JsonStringifyReplacement extends Replacement, DataFlow::CallNode {
JsonStringifyReplacement() { this = DataFlow::globalVarRef("JSON").getAMemberCall("stringify") }
override predicate replaces(string input, string output) {
input = "\\" and output = "\\\\"
// the other replacements are not relevant for this query
}
override DataFlow::Node getInput() { result = this.getArgument(0) }
override DataFlow::SourceNode getOutput() { result = this }
}
/**
* A call to `JSON.parse`, viewed as a string replacement.
*/
class JsonParseReplacement extends Replacement {
JsonParserCall self;
JsonParseReplacement() { this = self }
override predicate replaces(string input, string output) {
input = "\\\\" and output = "\\"
// the other replacements are not relevant for this query
}
override DataFlow::Node getInput() { result = self.getInput() }
override DataFlow::SourceNode getOutput() { result = self.getOutput() }
}
/**
* A string replacement wrapped in a utility function.
*/
class WrappedReplacement extends Replacement, DataFlow::CallNode {
int i;
Replacement inner;
WrappedReplacement() {
exists(DataFlow::FunctionNode wrapped | wrapped.getFunction() = getACallee() |
wrapped.getParameter(i).flowsTo(inner.getPreviousReplacement*().getInput()) and
inner.getNextReplacement*().getOutput().flowsTo(wrapped.getAReturn())
)
}
override predicate replaces(string input, string output) { inner.replaces(input, output) }
override DataFlow::Node getInput() { result = getArgument(i) }
override DataFlow::SourceNode getOutput() { result = this }
}
from Replacement primary, Replacement supplementary, string message, string metachar
where
primary.escapes(metachar, _) and

View File

@@ -7,7 +7,7 @@
* @id js/loop-bound-injection
* @tags security
* external/cwe/cwe-834
* @precision medium
* @precision high
*/
import javascript

View File

@@ -14,17 +14,19 @@ import PortalExitSource
import PortalEntrySink
from
TaintTracking::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p1,
Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2
TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p1,
Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, DataFlow::MidPathNode last
where
cfg.hasFlowPath(source, sink) and
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p1 = source.getNode().(PortalExitSource).getPortal() and
p2 = sink.getNode().(PortalEntrySink).getPortal() and
lbl1 = sink.getPathSummary().getStartLabel() and
lbl2 = sink.getPathSummary().getEndLabel() and
lbl1 = last.getPathSummary().getStartLabel() and
lbl2 = last.getPathSummary().getEndLabel() and
// avoid constructing infeasible paths
sink.getPathSummary().hasCall() = false and
sink.getPathSummary().hasReturn() = false and
last.getPathSummary().hasCall() = false and
last.getPathSummary().hasReturn() = false and
// restrict to steps flow function parameters to returns
p1.(ParameterPortal).getBasePortal() = p2.(ReturnPortal).getBasePortal() and
// restrict to data/taint flow

View File

@@ -11,10 +11,13 @@ import Configurations
import PortalExitSource
import SinkFromAnnotation
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg.hasFlowPath(source, sink) and
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = source.getNode().(PortalExitSource).getPortal() and
// avoid constructing infeasible paths
sink.getPathSummary().hasReturn() = false
select p.toString(), source.getPathSummary().getStartLabel().toString(), cfg.toString()
last.getPathSummary().hasReturn() = false
select p.toString(), last.getPathSummary().getStartLabel().toString(), cfg.toString()

View File

@@ -11,10 +11,13 @@ import Configurations
import PortalEntrySink
import SourceFromAnnotation
from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Portal p
from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink,
Portal p, DataFlow::MidPathNode last
where
cfg.hasFlowPath(source, sink) and
cfg = source.getConfiguration() and
last = source.getASuccessor*() and
sink = last.getASuccessor() and
p = sink.getNode().(PortalEntrySink).getPortal() and
// avoid constructing infeasible paths
sink.getPathSummary().hasCall() = false
select p.toString(), sink.getPathSummary().getEndLabel().toString(), cfg.toString()
last.getPathSummary().hasCall() = false
select p.toString(), last.getPathSummary().getEndLabel().toString(), cfg.toString()

View File

@@ -1,51 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
On some platforms, the builtin function <code>parseInt</code> parses strings starting with the digit
<code>0</code> as octal values (unless an explicit radix is provided). This can lead to unexpected
results when parsing decimal numbers that may be zero-padded, such as dates.
</p>
</overview>
<recommendation>
<p>
Provide an explicit radix as the second parameter to <code>parseInt</code>.
</p>
</recommendation>
<example>
<p>
In the following example, <code>parseInt</code> is used to convert the contents of a field in an HTML
form to a number:
</p>
<sample src="examples/ParseIntRadix.js" />
<p>
Now assume that a user has entered a zero-padded decimal number, say <code>09</code>, into the form.
Since the first digit is a zero, older versions of <code>parseInt</code> interpret this value as an
octal number. When they then encounter <code>9</code> (which is not an octal digit), they will stop
parsing and discard the rest of the string, returning the value <code>0</code>, which is probably not
what was expected.
</p>
<p>
To avoid this problem, an explicit radix parameter should be parsed as follows:
</p>
<sample src="examples/ParseIntRadixGood.js" />
</example>
<references>
<li>D. Crockford, <i>JavaScript: The Good Parts</i>, Appendix A.7. O'Reilly, 2008.</li>
</references>
</qhelp>

View File

@@ -1,21 +0,0 @@
/**
* @name Call to parseInt without radix
* @description Calls to the 'parseInt' function should always specify a radix to avoid accidentally
* parsing a number as octal.
* @kind problem
* @problem.severity recommendation
* @id js/parseint-without-radix
* @tags reliability
* maintainability
* external/cwe/cwe-676
* @precision very-high
* @deprecated This is no longer a problem with modern browsers. Deprecated since 1.17.
*/
import javascript
from DataFlow::CallNode parseInt
where
parseInt = DataFlow::globalVarRef("parseInt").getACall() and
parseInt.getNumArgument() = 1
select parseInt, "Missing radix parameter."

View File

@@ -0,0 +1,43 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
JavaScript functions that do not return an expression will implicitly return
<code>undefined</code>. Using the implicit return value from such a function
is not an error in itself, but it is a pattern indicating that some
misunderstanding has occurred.
</p>
</overview>
<recommendation>
<p>
Do not use the return value from a function that does not return an expression.
</p>
</recommendation>
<example>
<p>
In the example below, the function <code>renderText</code> is used to render
text through side effects, and the function does not return an expression.
However, the programmer still uses the return value from
<code>renderText</code> as if the function returned an expression, which is
clearly an error.
</p>
<sample src="examples/UseOfReturnlessFunction.js" />
<p>
The program can be fixed either by removing the use of the value returned by
<code>renderText</code>, or by changing the <code>renderText</code> function
to return an expression.
</p>
</example>
<references>
<li>Mozilla Developer Network: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return">Return</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,168 @@
/**
* @name Use of returnless function
* @description Using the return value of a function that does not return an expression is indicative of a mistake.
* @kind problem
* @problem.severity warning
* @id js/use-of-returnless-function
* @tags maintainability
* correctness
* @precision high
*/
import javascript
import Declarations.UnusedVariable
import Expressions.ExprHasNoEffect
import Statements.UselessConditional
predicate returnsVoid(Function f) {
not f.isGenerator() and
not f.isAsync() and
not exists(f.getAReturnedExpr())
}
predicate isStub(Function f) {
f.getBody().(BlockStmt).getNumChild() = 0
or
f instanceof ExternalDecl
}
/**
* Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function.
*/
predicate benignContext(Expr e) {
inVoidContext(e) or
// A return statement is often used to just end the function.
e = any(Function f).getAReturnedExpr()
or
exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond))
or
exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin))
or
exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent))
or
any(VoidExpr voidExpr).getOperand() = e
or
// weeds out calls inside HTML-attributes.
e.getParent() instanceof CodeInAttribute or
// and JSX-attributes.
e = any(JSXAttribute attr).getValue() or
exists(AwaitExpr await | await.getOperand() = e and benignContext(await))
or
// Avoid double reporting with js/trivial-conditional
isExplicitConditional(_, e)
or
// Avoid double reporting with js/comparison-between-incompatible-types
any(Comparison binOp).getAnOperand() = e
or
// Avoid double reporting with js/property-access-on-non-object
any(PropAccess ac).getBase() = e
or
// Avoid double-reporting with js/unused-local-variable
exists(VariableDeclarator v | v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal)
or
// Avoid double reporting with js/call-to-non-callable
any(InvokeExpr invoke).getCallee() = e
or
// arguments to Promise.resolve (and promise library variants) are benign.
e = any(ResolvedPromiseDefinition promise).getValue().asExpr()
}
predicate oneshotClosure(InvokeExpr call) {
call.getCallee().getUnderlyingValue() instanceof ImmediatelyInvokedFunctionExpr
}
predicate alwaysThrows(Function f) {
exists(ReachableBasicBlock entry, DataFlow::Node throwNode |
entry = f.getEntryBB() and
throwNode.asExpr() = any(ThrowStmt t).getExpr() and
entry.dominates(throwNode.getBasicBlock())
)
}
/**
* Holds if the last statement in the function is flagged by the js/useless-expression query.
*/
predicate lastStatementHasNoEffect(Function f) {
hasNoEffect(f.getExit().getAPredecessor())
}
/**
* Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value.
*/
predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
not call.isIncomplete() and
func = call.getACallee() and
forall(Function f | f = call.getACallee() |
returnsVoid(f) and not isStub(f) and not alwaysThrows(f)
)
}
/**
* Holds if `name` is the name of a method from `Array.prototype` or Lodash,
* where that method takes a callback as parameter,
* and the callback is expected to return a value.
*/
predicate hasNonVoidCallbackMethod(string name) {
name = "every" or
name = "filter" or
name = "find" or
name = "findIndex" or
name = "flatMap" or
name = "map" or
name = "reduce" or
name = "reduceRight" or
name = "some" or
name = "sort"
}
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
t.start() and result instanceof DataFlow::ArrayCreationNode
or
exists (DataFlow::TypeTracker t2 |
result = array(t2).track(t2, t)
)
}
DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) }
/**
* Holds if `call` is an Array or Lodash method accepting a callback `func`,
* where the `call` expects a callback that returns an expression,
* but `func` does not return a value.
*/
predicate voidArrayCallback(DataFlow::CallNode call, Function func) {
hasNonVoidCallbackMethod(call.getCalleeName()) and
exists(int index |
index = min(int i | exists(call.getCallback(i))) and
func = call.getCallback(index).getFunction()
) and
returnsVoid(func) and
not isStub(func) and
not alwaysThrows(func) and
(
call.getReceiver().getALocalSource() = array()
or
call.getCalleeNode().getALocalSource() instanceof LodashUnderscore::Member
)
}
from DataFlow::CallNode call, Function func, string name, string msg
where
(
callToVoidFunction(call, func) and
msg = "the $@ does not return anything, yet the return value is used." and
name = func.describe()
or
voidArrayCallback(call, func) and
msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + " is used." and
name = "callback function"
) and
not benignContext(call.asExpr()) and
not lastStatementHasNoEffect(func) and
// anonymous one-shot closure. Those are used in weird ways and we ignore them.
not oneshotClosure(call.asExpr())
select
call, msg, func, name

View File

@@ -16,6 +16,7 @@ import javascript
import semmle.javascript.RestrictedLocations
import semmle.javascript.dataflow.Refinements
import semmle.javascript.DefensiveProgramming
import UselessConditional
/**
* Gets the unique definition of `v`.
@@ -123,22 +124,6 @@ predicate whitelist(Expr e) {
isConstantBooleanReturnValue(e)
}
/**
* Holds if `e` is part of a conditional node `cond` that evaluates
* `e` and checks its value for truthiness, and the return value of `e`
* is not used for anything other than this truthiness check.
*/
predicate isExplicitConditional(ASTNode cond, Expr e) {
e = cond.(IfStmt).getCondition()
or
e = cond.(LoopStmt).getTest()
or
e = cond.(ConditionalExpr).getCondition()
or
isExplicitConditional(_, cond) and
e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand()
}
/**
* Holds if `e` is part of a conditional node `cond` that evaluates
* `e` and checks its value for truthiness.

View File

@@ -0,0 +1,21 @@
/**
* Provides predicates for working with useless conditionals.
*/
import javascript
/**
* Holds if `e` is part of a conditional node `cond` that evaluates
* `e` and checks its value for truthiness, and the return value of `e`
* is not used for anything other than this truthiness check.
*/
predicate isExplicitConditional(ASTNode cond, Expr e) {
e = cond.(IfStmt).getCondition()
or
e = cond.(LoopStmt).getTest()
or
e = cond.(ConditionalExpr).getCondition()
or
isExplicitConditional(_, cond) and
e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand()
}

View File

@@ -0,0 +1,9 @@
var stage = require("./stage")
function renderText(text, id) {
document.getElementById(id).innerText = text;
}
var text = renderText("Two households, both alike in dignity", "scene");
stage.show(text);

View File

@@ -0,0 +1,4 @@
- description: Standard LGTM queries for JavaScript, including ones not displayed by default
- qlpack: codeql-javascript
- apply: lgtm-selectors.yml
from: codeql-suite-helpers

View File

@@ -0,0 +1,4 @@
- description: Standard LGTM queries for JavaScript
- apply: codeql-suites/javascript-lgtm-full.qls
- apply: lgtm-displayed-only.yml
from: codeql-suite-helpers

View File

@@ -47,97 +47,6 @@ class RelevantFunction extends Function {
}
}
/**
* Holds if `name` is a the name of an external module.
*/
predicate isExternalLibrary(string name) {
// Mentioned in package.json
any(Dependency dep).info(name, _) or
// Node.js built-in
name = "assert" or
name = "async_hooks" or
name = "child_process" or
name = "cluster" or
name = "crypto" or
name = "dns" or
name = "domain" or
name = "events" or
name = "fs" or
name = "http" or
name = "http2" or
name = "https" or
name = "inspector" or
name = "net" or
name = "os" or
name = "path" or
name = "perf_hooks" or
name = "process" or
name = "punycode" or
name = "querystring" or
name = "readline" or
name = "repl" or
name = "stream" or
name = "string_decoder" or
name = "timer" or
name = "tls" or
name = "trace_events" or
name = "tty" or
name = "dgram" or
name = "url" or
name = "util" or
name = "v8" or
name = "vm" or
name = "worker_threads" or
name = "zlib"
}
/**
* Holds if the global variable `name` is defined externally.
*/
predicate isExternalGlobal(string name) {
exists(ExternalGlobalDecl decl | decl.getName() = name)
or
exists(Dependency dep |
// If name is never assigned anywhere, and it coincides with a dependency,
// it's most likely coming from there.
dep.info(name, _) and
not exists(Assignment assign | assign.getLhs().(GlobalVarAccess).getName() = name)
)
or
name = "_"
}
/**
* Gets a node that was derived from an import of `moduleName`.
*
* This is a rough approximation as it follows all property reads, invocations,
* and callbacks, so some of these might refer to internal objects.
*
* Additionally, we don't recognize when a project imports another file in the
* same project using its module name (for example import "vscode" from inside the vscode project).
*/
SourceNode externalNode() {
exists(string moduleName |
result = moduleImport(moduleName) and
isExternalLibrary(moduleName)
)
or
exists(string name |
result = globalVarRef(name) and
isExternalGlobal(name)
)
or
result = DOM::domValueRef()
or
result = jquery()
or
result = externalNode().getAPropertyRead()
or
result = externalNode().getAnInvocation()
or
result = externalNode().(InvokeNode).getCallback(_).getParameter(_)
}
/**
* Gets a data flow node that can be resolved to a function, usually a callback.
*
@@ -167,7 +76,7 @@ SourceNode nodeLeadingToInvocation() {
result.flowsTo(arg)
)
or
exists(AdditionalPartialInvokeNode invoke, Node arg |
exists(PartialInvokeNode invoke, Node arg |
invoke.isPartialArgument(arg, _, _) and
result.flowsTo(arg)
)
@@ -192,49 +101,15 @@ class ResolvableCall extends RelevantInvoke {
}
}
/**
* A call site that is believed to call an external function.
*/
class ExternalCall extends RelevantInvoke {
ExternalCall() {
not this instanceof ResolvableCall and // avoid double counting
(
// Call to modelled external library
this = externalNode()
or
// 'require' call or similar
this = moduleImport(_)
or
// Resolved to externs file
exists(this.(InvokeNode).getACallee(1))
or
// Modelled as taint step but isn't from an NPM module, for example, `substring` or `push`.
exists(TaintTracking::AdditionalTaintStep step |
step.step(_, this)
or
step.step(this.getAnArgument(), _)
)
)
}
}
/**
* A call site that could not be resolved.
*/
class UnresolvableCall extends RelevantInvoke {
UnresolvableCall() {
not this instanceof ResolvableCall and
not this instanceof ExternalCall
not this instanceof ResolvableCall
}
}
/**
* A call that is believed to call a function within the same project.
*/
class NonExternalCall extends RelevantInvoke {
NonExternalCall() { not this instanceof ExternalCall }
}
/**
* A function with at least one call site.
*/

View File

@@ -0,0 +1,4 @@
name: codeql-javascript
version: 0.0.0
dbscheme: semmlecode.javascript.dbscheme
suites: codeql-suites

View File

@@ -0,0 +1,97 @@
/**
* Provides classes for reasoning about character escapes in literals.
*/
import javascript
module CharacterEscapes {
/**
* Provides sets of characters (as strings) with specific string/regexp characteristics.
*/
private module Sets {
string sharedEscapeChars() { result = "fnrtvux0\\" }
string regexpAssertionChars() { result = "bB" }
string regexpCharClassChars() { result = "cdDpPsSwW" }
string regexpBackreferenceChars() { result = "123456789k" }
string regexpMetaChars() { result = "^$*+?.()|{}[]-" }
}
/**
* Gets the `i`th character of `raw`, which is preceded by an uneven number of backslashes.
*/
bindingset[raw]
string getAnEscapedCharacter(string raw, int i) {
result = raw.regexpFind("(?<=(^|[^\\\\])\\\\(\\\\{2}){0,10}).", _, i)
}
/**
* Holds if `n` is delimited by `delim` and contains `rawStringNode` with the raw string value `raw`.
*/
private predicate hasRawStringAndQuote(
DataFlow::ValueNode n, string delim, ASTNode rawStringNode, string raw
) {
rawStringNode = n.asExpr() and
raw = rawStringNode.(StringLiteral).getRawValue() and
delim = raw.charAt(0)
or
rawStringNode = n.asExpr().(TemplateLiteral).getAnElement() and
raw = rawStringNode.(TemplateElement).getRawValue() and
delim = "`"
or
rawStringNode = n.asExpr() and
raw = rawStringNode.(RegExpLiteral).getRawValue() and
delim = "/"
}
/**
* Gets a character in `n` that is preceded by a single useless backslash.
*
* The character is the `i`th character of `rawStringNode`'s raw string value.
*/
string getAnIdentityEscapedCharacter(DataFlow::Node n, ASTNode rawStringNode, int i) {
exists(string delim, string raw, string additionalEscapeChars |
hasRawStringAndQuote(n, delim, rawStringNode, raw) and
if rawStringNode instanceof RegExpLiteral
then
additionalEscapeChars = Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() +
Sets::regexpBackreferenceChars()
else additionalEscapeChars = "b"
|
result = getAnEscapedCharacter(raw, i) and
not result = (Sets::sharedEscapeChars() + delim + additionalEscapeChars).charAt(_)
)
}
/**
* Holds if `src` likely contains a regular expression mistake, to be reported by `js/useless-regexp-character-escape`.
*/
predicate hasALikelyRegExpPatternMistake(RegExpPatternSource src) {
exists(getALikelyRegExpPatternMistake(src, _, _, _))
}
/**
* Gets a character in `n` that is preceded by a single useless backslash, resulting in a likely regular expression mistake explained by `mistake`.
*
* The character is the `i`th character of the raw string value of `rawStringNode`.
*/
string getALikelyRegExpPatternMistake(
RegExpPatternSource src, string mistake, ASTNode rawStringNode, int i
) {
result = getAnIdentityEscapedCharacter(src, rawStringNode, i) and
(
result = Sets::regexpAssertionChars().charAt(_) and mistake = "is not an assertion"
or
result = Sets::regexpCharClassChars().charAt(_) and mistake = "is not a character class"
or
result = Sets::regexpBackreferenceChars().charAt(_) and mistake = "is not a backreference"
or
// conservative formulation: we do not know in general if the sequence is enclosed in a character class `[...]`
result = Sets::regexpMetaChars().charAt(_) and
mistake = "may still represent a meta-character"
)
}
}

Some files were not shown because too many files have changed in this diff Show More