circle-ir 3.23.5 → 3.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/passes/scan-secrets-pass.d.ts +60 -0
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -0
- package/dist/analysis/passes/scan-secrets-pass.js +345 -0
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -0
- package/dist/analyzer.d.ts +1 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +6 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +297 -6
- package/dist/core/circle-ir-core.cjs +28 -6
- package/dist/core/circle-ir-core.js +28 -6
- package/dist/core/extractors/types.js +32 -0
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +4 -7
- package/dist/core/parser.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +10 -0
- package/dist/graph/analysis-pass.d.ts.map +1 -1
- package/dist/graph/analysis-pass.js +3 -0
- package/dist/graph/analysis-pass.js.map +1 -1
- package/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/package.json +1 -1
|
@@ -4045,8 +4045,7 @@ async function loadLanguage(language, wasmPath) {
|
|
|
4045
4045
|
if (loading) {
|
|
4046
4046
|
return loading;
|
|
4047
4047
|
}
|
|
4048
|
-
const
|
|
4049
|
-
const wasmModule = configuredLanguageModules[language] ?? configuredLanguageModules[grammarName];
|
|
4048
|
+
const wasmModule = configuredLanguageModules[language];
|
|
4050
4049
|
if (wasmModule) {
|
|
4051
4050
|
const loadPromise2 = (async () => {
|
|
4052
4051
|
const lang = await Language.load(wasmModule);
|
|
@@ -4145,20 +4144,19 @@ async function getDefaultWasmPath() {
|
|
|
4145
4144
|
return "node_modules/web-tree-sitter/web-tree-sitter.wasm";
|
|
4146
4145
|
}
|
|
4147
4146
|
async function getDefaultLanguagePath(language) {
|
|
4148
|
-
const grammarName = language === "typescript" ? "javascript" : language;
|
|
4149
4147
|
const mods = await getNodeModules();
|
|
4150
4148
|
if (mods && moduleDir) {
|
|
4151
4149
|
const packageRoot = mods.join(moduleDir, "..", "..");
|
|
4152
|
-
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${
|
|
4150
|
+
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${language}.wasm`);
|
|
4153
4151
|
if (mods.existsSync(distWasmPath)) {
|
|
4154
4152
|
return distWasmPath;
|
|
4155
4153
|
}
|
|
4156
|
-
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${
|
|
4154
|
+
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${language}.wasm`);
|
|
4157
4155
|
if (mods.existsSync(packageWasmPath)) {
|
|
4158
4156
|
return packageWasmPath;
|
|
4159
4157
|
}
|
|
4160
4158
|
}
|
|
4161
|
-
return `wasm/tree-sitter-${
|
|
4159
|
+
return `wasm/tree-sitter-${language}.wasm`;
|
|
4162
4160
|
}
|
|
4163
4161
|
|
|
4164
4162
|
// src/core/extractors/meta.ts
|
|
@@ -4856,6 +4854,30 @@ function extractJSParameters(params) {
|
|
|
4856
4854
|
annotations: [],
|
|
4857
4855
|
line: child.startPosition.row + 1
|
|
4858
4856
|
});
|
|
4857
|
+
} else if (child.type === "required_parameter" || child.type === "optional_parameter") {
|
|
4858
|
+
const patternNode = child.childForFieldName("pattern");
|
|
4859
|
+
if (!patternNode) continue;
|
|
4860
|
+
let paramName;
|
|
4861
|
+
if (patternNode.type === "identifier") {
|
|
4862
|
+
paramName = getNodeText(patternNode);
|
|
4863
|
+
} else if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
|
|
4864
|
+
const inner = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
4865
|
+
if (!inner) continue;
|
|
4866
|
+
paramName = "..." + getNodeText(inner);
|
|
4867
|
+
} else {
|
|
4868
|
+
paramName = getNodeText(patternNode);
|
|
4869
|
+
}
|
|
4870
|
+
const typeNode = child.childForFieldName("type");
|
|
4871
|
+
let paramType = null;
|
|
4872
|
+
if (typeNode) {
|
|
4873
|
+
paramType = getNodeText(typeNode).replace(/^:\s*/, "");
|
|
4874
|
+
}
|
|
4875
|
+
parameters.push({
|
|
4876
|
+
name: paramName,
|
|
4877
|
+
type: paramType,
|
|
4878
|
+
annotations: [],
|
|
4879
|
+
line: child.startPosition.row + 1
|
|
4880
|
+
});
|
|
4859
4881
|
}
|
|
4860
4882
|
}
|
|
4861
4883
|
return parameters;
|
|
@@ -13280,6 +13302,9 @@ var AnalysisPipeline = class {
|
|
|
13280
13302
|
},
|
|
13281
13303
|
addFinding(finding) {
|
|
13282
13304
|
findings.push(finding);
|
|
13305
|
+
},
|
|
13306
|
+
getFindings() {
|
|
13307
|
+
return findings;
|
|
13283
13308
|
}
|
|
13284
13309
|
};
|
|
13285
13310
|
for (const pass of this.passes) {
|
|
@@ -24991,6 +25016,271 @@ function detectHandler(graph, calls) {
|
|
|
24991
25016
|
return false;
|
|
24992
25017
|
}
|
|
24993
25018
|
|
|
25019
|
+
// src/analysis/passes/scan-secrets-pass.ts
|
|
25020
|
+
var TEST_PATH_RE3 = /(?:^|[\\/])(?:test|tests|spec|specs|__tests?__|__mocks?__|fixtures?|testdata)(?:[\\/]|$)/i;
|
|
25021
|
+
var TEST_FILENAME_RE = /(?:\.(?:test|spec)\.[cm]?[jt]sx?|_test\.go|_test\.py|Test\.java|Tests\.java)$/i;
|
|
25022
|
+
function isTestFile(file) {
|
|
25023
|
+
return TEST_PATH_RE3.test(file) || TEST_FILENAME_RE.test(file);
|
|
25024
|
+
}
|
|
25025
|
+
var PROVIDER_PATTERNS = [
|
|
25026
|
+
{
|
|
25027
|
+
name: "AWS access key",
|
|
25028
|
+
regex: /\bAKIA[0-9A-Z]{16}\b/,
|
|
25029
|
+
severity: "critical",
|
|
25030
|
+
level: "error",
|
|
25031
|
+
fix: "Rotate the AWS access key immediately and move it to an environment variable or AWS Secrets Manager."
|
|
25032
|
+
},
|
|
25033
|
+
{
|
|
25034
|
+
name: "GitHub personal access token",
|
|
25035
|
+
regex: /\bghp_[A-Za-z0-9]{36}\b/,
|
|
25036
|
+
severity: "critical",
|
|
25037
|
+
level: "error",
|
|
25038
|
+
fix: "Revoke the token at https://github.com/settings/tokens and store secrets in CI/CD secrets, not source."
|
|
25039
|
+
},
|
|
25040
|
+
{
|
|
25041
|
+
name: "GitHub OAuth token",
|
|
25042
|
+
regex: /\bgho_[A-Za-z0-9]{36}\b/,
|
|
25043
|
+
severity: "critical",
|
|
25044
|
+
level: "error",
|
|
25045
|
+
fix: "Revoke the OAuth token and store secrets outside source control."
|
|
25046
|
+
},
|
|
25047
|
+
{
|
|
25048
|
+
name: "GitHub user-to-server token",
|
|
25049
|
+
regex: /\bghu_[A-Za-z0-9]{36}\b/,
|
|
25050
|
+
severity: "critical",
|
|
25051
|
+
level: "error",
|
|
25052
|
+
fix: "Revoke the GitHub user-to-server token and store secrets outside source control."
|
|
25053
|
+
},
|
|
25054
|
+
{
|
|
25055
|
+
name: "GitHub server-to-server token",
|
|
25056
|
+
regex: /\bghs_[A-Za-z0-9]{36}\b/,
|
|
25057
|
+
severity: "critical",
|
|
25058
|
+
level: "error",
|
|
25059
|
+
fix: "Revoke the GitHub server-to-server token and store secrets outside source control."
|
|
25060
|
+
},
|
|
25061
|
+
{
|
|
25062
|
+
name: "GitHub refresh token",
|
|
25063
|
+
regex: /\bghr_[A-Za-z0-9]{36}\b/,
|
|
25064
|
+
severity: "critical",
|
|
25065
|
+
level: "error",
|
|
25066
|
+
fix: "Revoke the GitHub refresh token and store secrets outside source control."
|
|
25067
|
+
},
|
|
25068
|
+
{
|
|
25069
|
+
name: "Stripe live secret key",
|
|
25070
|
+
regex: /\bsk_live_[A-Za-z0-9]{24,}\b/,
|
|
25071
|
+
severity: "critical",
|
|
25072
|
+
level: "error",
|
|
25073
|
+
fix: "Rotate the Stripe secret key in the Stripe Dashboard and load it from a secrets manager."
|
|
25074
|
+
},
|
|
25075
|
+
{
|
|
25076
|
+
name: "Stripe live publishable key",
|
|
25077
|
+
regex: /\bpk_live_[A-Za-z0-9]{24,}\b/,
|
|
25078
|
+
severity: "high",
|
|
25079
|
+
level: "warning",
|
|
25080
|
+
fix: "Publishable keys are not secret but should still not be checked in to back-end source files; verify front-end vs back-end context."
|
|
25081
|
+
},
|
|
25082
|
+
{
|
|
25083
|
+
name: "OpenAI API key",
|
|
25084
|
+
regex: /\bsk-[A-Za-z0-9]{48}\b/,
|
|
25085
|
+
severity: "critical",
|
|
25086
|
+
level: "error",
|
|
25087
|
+
fix: "Revoke the OpenAI key at https://platform.openai.com/api-keys and load from environment."
|
|
25088
|
+
},
|
|
25089
|
+
{
|
|
25090
|
+
name: "Anthropic API key",
|
|
25091
|
+
regex: /\bsk-ant-[A-Za-z0-9_-]{90,}\b/,
|
|
25092
|
+
severity: "critical",
|
|
25093
|
+
level: "error",
|
|
25094
|
+
fix: "Revoke the Anthropic key in the Console and load from environment."
|
|
25095
|
+
},
|
|
25096
|
+
{
|
|
25097
|
+
name: "Slack token",
|
|
25098
|
+
regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
25099
|
+
severity: "critical",
|
|
25100
|
+
level: "error",
|
|
25101
|
+
fix: "Revoke the Slack token and load from environment."
|
|
25102
|
+
},
|
|
25103
|
+
{
|
|
25104
|
+
name: "Google API key",
|
|
25105
|
+
regex: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
25106
|
+
severity: "critical",
|
|
25107
|
+
level: "error",
|
|
25108
|
+
fix: "Restrict the Google API key by referrer / IP in the GCP console or revoke it."
|
|
25109
|
+
},
|
|
25110
|
+
{
|
|
25111
|
+
name: "JSON Web Token",
|
|
25112
|
+
regex: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/,
|
|
25113
|
+
severity: "critical",
|
|
25114
|
+
level: "error",
|
|
25115
|
+
fix: "JWTs in source carry whatever scope they were minted with; rotate signing keys and remove the token."
|
|
25116
|
+
},
|
|
25117
|
+
{
|
|
25118
|
+
name: "PEM private key",
|
|
25119
|
+
regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/,
|
|
25120
|
+
severity: "critical",
|
|
25121
|
+
level: "error",
|
|
25122
|
+
fix: "Remove the private key from source control immediately, rotate the corresponding public key, and store keys outside the repository."
|
|
25123
|
+
},
|
|
25124
|
+
{
|
|
25125
|
+
name: "npm access token",
|
|
25126
|
+
regex: /\bnpm_[A-Za-z0-9]{36}\b/,
|
|
25127
|
+
severity: "critical",
|
|
25128
|
+
level: "error",
|
|
25129
|
+
fix: "Revoke the npm token at https://www.npmjs.com/settings/<user>/tokens and load from environment."
|
|
25130
|
+
}
|
|
25131
|
+
];
|
|
25132
|
+
var STRING_LITERAL_RE = /(["'`])((?:\\.|(?!\1).){8,200})\1/g;
|
|
25133
|
+
var BASE64ISH_RE = /^[A-Za-z0-9+/=_-]+$/;
|
|
25134
|
+
var HEXISH_RE = /^[a-fA-F0-9]+$/;
|
|
25135
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
25136
|
+
var PLACEHOLDER_RE = /(?:changeme|your[-_]?(?:key|secret|token|password)(?:[-_]?here)?|replace[-_]?me|example[-_]?(?:key|secret|token)?|placeholder|todo|fixme|test[-_]?(?:key|secret|token)|fake[-_]?(?:key|secret|token)|dummy|sample|insert[-_]?your)/i;
|
|
25137
|
+
function isBareHashShape(s) {
|
|
25138
|
+
const n = s.length;
|
|
25139
|
+
if (n !== 32 && n !== 40 && n !== 64) return false;
|
|
25140
|
+
return HEXISH_RE.test(s);
|
|
25141
|
+
}
|
|
25142
|
+
function isAllSameChar(s) {
|
|
25143
|
+
if (s.length < 2) return false;
|
|
25144
|
+
const c = s.charAt(0);
|
|
25145
|
+
for (let i2 = 1; i2 < s.length; i2++) if (s.charAt(i2) !== c) return false;
|
|
25146
|
+
return true;
|
|
25147
|
+
}
|
|
25148
|
+
function tryBase64Decode(s) {
|
|
25149
|
+
if (s.length % 4 !== 0 && !/=+$/.test(s)) return null;
|
|
25150
|
+
try {
|
|
25151
|
+
return globalThis.atob(s);
|
|
25152
|
+
} catch {
|
|
25153
|
+
return null;
|
|
25154
|
+
}
|
|
25155
|
+
}
|
|
25156
|
+
function looksLikeBase64Json(s) {
|
|
25157
|
+
const decoded = tryBase64Decode(s);
|
|
25158
|
+
if (!decoded) return false;
|
|
25159
|
+
const trimmed = decoded.trimStart();
|
|
25160
|
+
return trimmed.startsWith("{") || trimmed.startsWith("[");
|
|
25161
|
+
}
|
|
25162
|
+
function shannonEntropy(s) {
|
|
25163
|
+
const freq = /* @__PURE__ */ new Map();
|
|
25164
|
+
for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
25165
|
+
const len = s.length;
|
|
25166
|
+
let h = 0;
|
|
25167
|
+
for (const n of freq.values()) {
|
|
25168
|
+
const p = n / len;
|
|
25169
|
+
h -= p * Math.log2(p);
|
|
25170
|
+
}
|
|
25171
|
+
return h;
|
|
25172
|
+
}
|
|
25173
|
+
var CREDENTIAL_NAME_RE = /(?:key|secret|token|password|passwd|credential|api[_-]?key)/i;
|
|
25174
|
+
var TEST_CALL_RE = /\b(?:expect|assert|describe|it|test)\s*\(/;
|
|
25175
|
+
var COMMENT_EXAMPLE_RE = /(?:\/\/|#)\s*(?:example|sample|test|fixture)/i;
|
|
25176
|
+
var ScanSecretsPass = class {
|
|
25177
|
+
name = "scan-secrets";
|
|
25178
|
+
category = "security";
|
|
25179
|
+
run(ctx) {
|
|
25180
|
+
const file = ctx.graph.ir.meta.file;
|
|
25181
|
+
if (isTestFile(file)) {
|
|
25182
|
+
return { providerFindings: 0, entropyFindings: 0 };
|
|
25183
|
+
}
|
|
25184
|
+
const lines = ctx.code.split("\n");
|
|
25185
|
+
const prior = ctx.getFindings?.() ?? [];
|
|
25186
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25187
|
+
for (const f of prior) {
|
|
25188
|
+
if (f.file !== file) continue;
|
|
25189
|
+
if (f.rule_id === "hardcoded-credential" || f.rule_id === "hardcoded-credential-entropy") {
|
|
25190
|
+
seen.add(`${f.line}:${f.rule_id}`);
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
let providerFindings = 0;
|
|
25194
|
+
let entropyFindings = 0;
|
|
25195
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25196
|
+
const lineText = lines[i2];
|
|
25197
|
+
const lineNum = i2 + 1;
|
|
25198
|
+
for (const pattern of PROVIDER_PATTERNS) {
|
|
25199
|
+
const m = pattern.regex.exec(lineText);
|
|
25200
|
+
if (!m) continue;
|
|
25201
|
+
const key = `${lineNum}:hardcoded-credential`;
|
|
25202
|
+
if (seen.has(key)) continue;
|
|
25203
|
+
seen.add(key);
|
|
25204
|
+
ctx.addFinding({
|
|
25205
|
+
id: `hardcoded-credential-${file}-${lineNum}`,
|
|
25206
|
+
pass: this.name,
|
|
25207
|
+
category: this.category,
|
|
25208
|
+
rule_id: "hardcoded-credential",
|
|
25209
|
+
cwe: "CWE-798",
|
|
25210
|
+
severity: pattern.severity,
|
|
25211
|
+
level: pattern.level,
|
|
25212
|
+
message: `Hardcoded credential: ${pattern.name} detected`,
|
|
25213
|
+
file,
|
|
25214
|
+
line: lineNum,
|
|
25215
|
+
snippet: lineText.trim().substring(0, 120),
|
|
25216
|
+
fix: pattern.fix,
|
|
25217
|
+
evidence: { provider: pattern.name, match: m[0].substring(0, 40) }
|
|
25218
|
+
});
|
|
25219
|
+
providerFindings += 1;
|
|
25220
|
+
break;
|
|
25221
|
+
}
|
|
25222
|
+
}
|
|
25223
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
25224
|
+
const lineText = lines[i2];
|
|
25225
|
+
const lineNum = i2 + 1;
|
|
25226
|
+
if (TEST_CALL_RE.test(lineText)) continue;
|
|
25227
|
+
if (COMMENT_EXAMPLE_RE.test(lineText)) continue;
|
|
25228
|
+
STRING_LITERAL_RE.lastIndex = 0;
|
|
25229
|
+
let match;
|
|
25230
|
+
while ((match = STRING_LITERAL_RE.exec(lineText)) !== null) {
|
|
25231
|
+
const value = match[2];
|
|
25232
|
+
if (!this.isCandidate(value)) continue;
|
|
25233
|
+
if (!this.passesEntropyGate(value, lineText)) continue;
|
|
25234
|
+
const key = `${lineNum}:hardcoded-credential-entropy`;
|
|
25235
|
+
if (seen.has(key)) continue;
|
|
25236
|
+
if (seen.has(`${lineNum}:hardcoded-credential`)) continue;
|
|
25237
|
+
seen.add(key);
|
|
25238
|
+
ctx.addFinding({
|
|
25239
|
+
id: `hardcoded-credential-entropy-${file}-${lineNum}`,
|
|
25240
|
+
pass: this.name,
|
|
25241
|
+
category: this.category,
|
|
25242
|
+
rule_id: "hardcoded-credential-entropy",
|
|
25243
|
+
cwe: "CWE-798",
|
|
25244
|
+
severity: "high",
|
|
25245
|
+
level: "warning",
|
|
25246
|
+
message: `Possible hardcoded secret: high-entropy string literal (${value.length} chars)`,
|
|
25247
|
+
file,
|
|
25248
|
+
line: lineNum,
|
|
25249
|
+
snippet: lineText.trim().substring(0, 120),
|
|
25250
|
+
fix: "If this is a credential, move it to environment / secrets manager. If it is sample data, add an `example` / `test` marker or disable this pass via `disabledPasses: ['scan-secrets']`.",
|
|
25251
|
+
evidence: { kind: "entropy", length: value.length }
|
|
25252
|
+
});
|
|
25253
|
+
entropyFindings += 1;
|
|
25254
|
+
}
|
|
25255
|
+
}
|
|
25256
|
+
return { providerFindings, entropyFindings };
|
|
25257
|
+
}
|
|
25258
|
+
/** Length + shape + denylist filter before entropy is computed. */
|
|
25259
|
+
isCandidate(s) {
|
|
25260
|
+
if (s.length < 20 || s.length > 200) return false;
|
|
25261
|
+
if (!BASE64ISH_RE.test(s) && !HEXISH_RE.test(s)) return false;
|
|
25262
|
+
if (UUID_RE.test(s)) return false;
|
|
25263
|
+
if (isBareHashShape(s)) return false;
|
|
25264
|
+
if (isAllSameChar(s)) return false;
|
|
25265
|
+
if (PLACEHOLDER_RE.test(s)) return false;
|
|
25266
|
+
if (looksLikeBase64Json(s)) return false;
|
|
25267
|
+
return true;
|
|
25268
|
+
}
|
|
25269
|
+
/**
|
|
25270
|
+
* Shannon-entropy gate. Base64-shaped strings need higher entropy than
|
|
25271
|
+
* hex-shaped (hex alphabet is 4 bits/char by construction). When the
|
|
25272
|
+
* surrounding line contains a credential-shaped variable name, both
|
|
25273
|
+
* thresholds drop by 0.2 bits/char.
|
|
25274
|
+
*/
|
|
25275
|
+
passesEntropyGate(value, lineText) {
|
|
25276
|
+
const isHex = HEXISH_RE.test(value);
|
|
25277
|
+
const boost = CREDENTIAL_NAME_RE.test(lineText) ? 0.2 : 0;
|
|
25278
|
+
const threshold = isHex ? 3.5 - boost : 4.3 - boost;
|
|
25279
|
+
const h = shannonEntropy(value);
|
|
25280
|
+
return h >= threshold;
|
|
25281
|
+
}
|
|
25282
|
+
};
|
|
25283
|
+
|
|
24994
25284
|
// src/analysis/metrics/passes/size-metrics-pass.ts
|
|
24995
25285
|
var SizeMetricsPass = class {
|
|
24996
25286
|
name = "size-metrics";
|
|
@@ -25835,6 +26125,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
25835
26125
|
pipeline.add(new SinkFilterPass());
|
|
25836
26126
|
pipeline.add(new TaintPropagationPass());
|
|
25837
26127
|
pipeline.add(new InterproceduralPass());
|
|
26128
|
+
if (!disabledPasses.has("scan-secrets")) pipeline.add(new ScanSecretsPass());
|
|
25838
26129
|
if (!disabledPasses.has("dead-code")) pipeline.add(new DeadCodePass());
|
|
25839
26130
|
if (!disabledPasses.has("missing-await")) pipeline.add(new MissingAwaitPass());
|
|
25840
26131
|
if (!disabledPasses.has("n-plus-one")) pipeline.add(new NPlusOnePass());
|
|
@@ -4110,8 +4110,7 @@ async function loadLanguage(language, wasmPath) {
|
|
|
4110
4110
|
if (loading) {
|
|
4111
4111
|
return loading;
|
|
4112
4112
|
}
|
|
4113
|
-
const
|
|
4114
|
-
const wasmModule = configuredLanguageModules[language] ?? configuredLanguageModules[grammarName];
|
|
4113
|
+
const wasmModule = configuredLanguageModules[language];
|
|
4115
4114
|
if (wasmModule) {
|
|
4116
4115
|
const loadPromise2 = (async () => {
|
|
4117
4116
|
const lang = await Language.load(wasmModule);
|
|
@@ -4210,20 +4209,19 @@ async function getDefaultWasmPath() {
|
|
|
4210
4209
|
return "node_modules/web-tree-sitter/web-tree-sitter.wasm";
|
|
4211
4210
|
}
|
|
4212
4211
|
async function getDefaultLanguagePath(language) {
|
|
4213
|
-
const grammarName = language === "typescript" ? "javascript" : language;
|
|
4214
4212
|
const mods = await getNodeModules();
|
|
4215
4213
|
if (mods && moduleDir) {
|
|
4216
4214
|
const packageRoot = mods.join(moduleDir, "..", "..");
|
|
4217
|
-
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${
|
|
4215
|
+
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${language}.wasm`);
|
|
4218
4216
|
if (mods.existsSync(distWasmPath)) {
|
|
4219
4217
|
return distWasmPath;
|
|
4220
4218
|
}
|
|
4221
|
-
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${
|
|
4219
|
+
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${language}.wasm`);
|
|
4222
4220
|
if (mods.existsSync(packageWasmPath)) {
|
|
4223
4221
|
return packageWasmPath;
|
|
4224
4222
|
}
|
|
4225
4223
|
}
|
|
4226
|
-
return `wasm/tree-sitter-${
|
|
4224
|
+
return `wasm/tree-sitter-${language}.wasm`;
|
|
4227
4225
|
}
|
|
4228
4226
|
function isInitialized() {
|
|
4229
4227
|
return parserInitialized;
|
|
@@ -4932,6 +4930,30 @@ function extractJSParameters(params) {
|
|
|
4932
4930
|
annotations: [],
|
|
4933
4931
|
line: child.startPosition.row + 1
|
|
4934
4932
|
});
|
|
4933
|
+
} else if (child.type === "required_parameter" || child.type === "optional_parameter") {
|
|
4934
|
+
const patternNode = child.childForFieldName("pattern");
|
|
4935
|
+
if (!patternNode) continue;
|
|
4936
|
+
let paramName;
|
|
4937
|
+
if (patternNode.type === "identifier") {
|
|
4938
|
+
paramName = getNodeText(patternNode);
|
|
4939
|
+
} else if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
|
|
4940
|
+
const inner = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
4941
|
+
if (!inner) continue;
|
|
4942
|
+
paramName = "..." + getNodeText(inner);
|
|
4943
|
+
} else {
|
|
4944
|
+
paramName = getNodeText(patternNode);
|
|
4945
|
+
}
|
|
4946
|
+
const typeNode = child.childForFieldName("type");
|
|
4947
|
+
let paramType = null;
|
|
4948
|
+
if (typeNode) {
|
|
4949
|
+
paramType = getNodeText(typeNode).replace(/^:\s*/, "");
|
|
4950
|
+
}
|
|
4951
|
+
parameters.push({
|
|
4952
|
+
name: paramName,
|
|
4953
|
+
type: paramType,
|
|
4954
|
+
annotations: [],
|
|
4955
|
+
line: child.startPosition.row + 1
|
|
4956
|
+
});
|
|
4935
4957
|
}
|
|
4936
4958
|
}
|
|
4937
4959
|
return parameters;
|
|
@@ -4045,8 +4045,7 @@ async function loadLanguage(language, wasmPath) {
|
|
|
4045
4045
|
if (loading) {
|
|
4046
4046
|
return loading;
|
|
4047
4047
|
}
|
|
4048
|
-
const
|
|
4049
|
-
const wasmModule = configuredLanguageModules[language] ?? configuredLanguageModules[grammarName];
|
|
4048
|
+
const wasmModule = configuredLanguageModules[language];
|
|
4050
4049
|
if (wasmModule) {
|
|
4051
4050
|
const loadPromise2 = (async () => {
|
|
4052
4051
|
const lang = await Language.load(wasmModule);
|
|
@@ -4145,20 +4144,19 @@ async function getDefaultWasmPath() {
|
|
|
4145
4144
|
return "node_modules/web-tree-sitter/web-tree-sitter.wasm";
|
|
4146
4145
|
}
|
|
4147
4146
|
async function getDefaultLanguagePath(language) {
|
|
4148
|
-
const grammarName = language === "typescript" ? "javascript" : language;
|
|
4149
4147
|
const mods = await getNodeModules();
|
|
4150
4148
|
if (mods && moduleDir) {
|
|
4151
4149
|
const packageRoot = mods.join(moduleDir, "..", "..");
|
|
4152
|
-
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${
|
|
4150
|
+
const distWasmPath = mods.join(packageRoot, "dist", "wasm", `tree-sitter-${language}.wasm`);
|
|
4153
4151
|
if (mods.existsSync(distWasmPath)) {
|
|
4154
4152
|
return distWasmPath;
|
|
4155
4153
|
}
|
|
4156
|
-
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${
|
|
4154
|
+
const packageWasmPath = mods.join(packageRoot, "wasm", `tree-sitter-${language}.wasm`);
|
|
4157
4155
|
if (mods.existsSync(packageWasmPath)) {
|
|
4158
4156
|
return packageWasmPath;
|
|
4159
4157
|
}
|
|
4160
4158
|
}
|
|
4161
|
-
return `wasm/tree-sitter-${
|
|
4159
|
+
return `wasm/tree-sitter-${language}.wasm`;
|
|
4162
4160
|
}
|
|
4163
4161
|
function isInitialized() {
|
|
4164
4162
|
return parserInitialized;
|
|
@@ -4867,6 +4865,30 @@ function extractJSParameters(params) {
|
|
|
4867
4865
|
annotations: [],
|
|
4868
4866
|
line: child.startPosition.row + 1
|
|
4869
4867
|
});
|
|
4868
|
+
} else if (child.type === "required_parameter" || child.type === "optional_parameter") {
|
|
4869
|
+
const patternNode = child.childForFieldName("pattern");
|
|
4870
|
+
if (!patternNode) continue;
|
|
4871
|
+
let paramName;
|
|
4872
|
+
if (patternNode.type === "identifier") {
|
|
4873
|
+
paramName = getNodeText(patternNode);
|
|
4874
|
+
} else if (patternNode.type === "rest_pattern" || patternNode.type === "rest_element") {
|
|
4875
|
+
const inner = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
4876
|
+
if (!inner) continue;
|
|
4877
|
+
paramName = "..." + getNodeText(inner);
|
|
4878
|
+
} else {
|
|
4879
|
+
paramName = getNodeText(patternNode);
|
|
4880
|
+
}
|
|
4881
|
+
const typeNode = child.childForFieldName("type");
|
|
4882
|
+
let paramType = null;
|
|
4883
|
+
if (typeNode) {
|
|
4884
|
+
paramType = getNodeText(typeNode).replace(/^:\s*/, "");
|
|
4885
|
+
}
|
|
4886
|
+
parameters.push({
|
|
4887
|
+
name: paramName,
|
|
4888
|
+
type: paramType,
|
|
4889
|
+
annotations: [],
|
|
4890
|
+
line: child.startPosition.row + 1
|
|
4891
|
+
});
|
|
4870
4892
|
}
|
|
4871
4893
|
}
|
|
4872
4894
|
return parameters;
|
|
@@ -751,6 +751,38 @@ function extractJSParameters(params) {
|
|
|
751
751
|
line: child.startPosition.row + 1,
|
|
752
752
|
});
|
|
753
753
|
}
|
|
754
|
+
else if (child.type === 'required_parameter' || child.type === 'optional_parameter') {
|
|
755
|
+
// TypeScript-grammar parameter: pattern (identifier or destructuring) + optional type_annotation
|
|
756
|
+
const patternNode = child.childForFieldName('pattern');
|
|
757
|
+
if (!patternNode)
|
|
758
|
+
continue;
|
|
759
|
+
let paramName;
|
|
760
|
+
if (patternNode.type === 'identifier') {
|
|
761
|
+
paramName = getNodeText(patternNode);
|
|
762
|
+
}
|
|
763
|
+
else if (patternNode.type === 'rest_pattern' || patternNode.type === 'rest_element') {
|
|
764
|
+
const inner = patternNode.namedChildCount > 0 ? patternNode.namedChild(0) : null;
|
|
765
|
+
if (!inner)
|
|
766
|
+
continue;
|
|
767
|
+
paramName = '...' + getNodeText(inner);
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
// object_pattern, array_pattern, or assignment_pattern with default
|
|
771
|
+
paramName = getNodeText(patternNode);
|
|
772
|
+
}
|
|
773
|
+
const typeNode = child.childForFieldName('type');
|
|
774
|
+
let paramType = null;
|
|
775
|
+
if (typeNode) {
|
|
776
|
+
// type_annotation includes the leading ':'; strip it for storage parity with other languages
|
|
777
|
+
paramType = getNodeText(typeNode).replace(/^:\s*/, '');
|
|
778
|
+
}
|
|
779
|
+
parameters.push({
|
|
780
|
+
name: paramName,
|
|
781
|
+
type: paramType,
|
|
782
|
+
annotations: [],
|
|
783
|
+
line: child.startPosition.row + 1,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
754
786
|
}
|
|
755
787
|
return parameters;
|
|
756
788
|
}
|