diff --git a/package-lock.json b/package-lock.json index 7e871f8d..f314700f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,6 @@ "tree-sitter-cuda": "^0.21.1", "tree-sitter-dart": "^1.0.0", "tree-sitter-elixir": "^0.3.5", - "tree-sitter-erlang": "github:WhatsApp/tree-sitter-erlang#semver:*", "tree-sitter-fsharp": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", "tree-sitter-gleam": "github:gleam-lang/tree-sitter-gleam", "tree-sitter-go": "^0.25.0", @@ -1478,13 +1477,13 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ "linux" + ], + "libc": [ + "glibc" ] }, "node_modules/@optave/codegraph-linux-x64-gnu": { @@ -1494,13 +1493,13 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ "linux" + ], + "libc": [ + "glibc" ] }, "node_modules/@optave/codegraph-linux-x64-musl": { @@ -1510,13 +1509,13 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ "linux" + ], + "libc": [ + "musl" ] }, "node_modules/@optave/codegraph-win32-x64-msvc": { @@ -1564,21 +1563,20 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { @@ -1589,9 +1587,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", - "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", "dev": true, "license": "BSD-3-Clause" }, @@ -4593,9 +4591,9 @@ } }, "node_modules/hono": { - "version": "4.12.18", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz", - "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==", + "version": "4.12.25", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.25.tgz", + "integrity": "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ==", "license": "MIT", "optional": true, "engines": { @@ -6168,22 +6166,6 @@ "node": ">=10" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -6192,9 +6174,9 @@ "license": "MIT" }, "node_modules/protobufjs": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz", - "integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz", + "integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", @@ -6202,15 +6184,15 @@ "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.1", + "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -6241,9 +6223,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "optional": true, "dependencies": { @@ -7421,48 +7403,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tree-sitter-erlang": { - "version": "0.0.0", - "resolved": "git+ssh://git@github.com/WhatsApp/tree-sitter-erlang.git#e446ec60022a7cafe157805742b41c04b499cc5d", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "node-addon-api": "^7.1.0", - "node-gyp-build": "^4.8.0", - "prettier": "^2.2.1", - "tree-sitter-cli": "^0.23.0" - }, - "peerDependencies": { - "tree-sitter": "^0.22.4" - }, - "peerDependenciesMeta": { - "tree_sitter": { - "optional": true - } - } - }, - "node_modules/tree-sitter-erlang/node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tree-sitter-erlang/node_modules/tree-sitter-cli": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.23.2.tgz", - "integrity": "sha512-kPPXprOqREX+C/FgUp2Qpt9jd0vSwn+hOgjzVv/7hapdoWpa+VeWId53rf4oNNd29ikheF12BYtGD/W90feMbA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "tree-sitter": "cli.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/tree-sitter-fsharp": { "version": "0.3.0", "resolved": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", diff --git a/package.json b/package.json index 134f58d6..a9cb0852 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,6 @@ "tree-sitter-cuda": "^0.21.1", "tree-sitter-dart": "^1.0.0", "tree-sitter-elixir": "^0.3.5", - "tree-sitter-erlang": "github:WhatsApp/tree-sitter-erlang#semver:*", "tree-sitter-fsharp": "https://github.com/ionide/tree-sitter-fsharp/archive/refs/tags/0.3.0.tar.gz", "tree-sitter-gleam": "github:gleam-lang/tree-sitter-gleam", "tree-sitter-go": "^0.25.0", diff --git a/scripts/build-wasm.ts b/scripts/build-wasm.ts index 733e4c14..056babaa 100644 --- a/scripts/build-wasm.ts +++ b/scripts/build-wasm.ts @@ -211,7 +211,15 @@ const grammars = [ { name: 'tree-sitter-clojure', pkg: 'tree-sitter-clojure', sub: null }, { name: 'tree-sitter-julia', pkg: 'tree-sitter-julia', sub: null }, { name: 'tree-sitter-r', pkg: '@eagleoutice/tree-sitter-r', sub: null }, - { name: 'tree-sitter-erlang', pkg: 'tree-sitter-erlang', sub: null }, + // tree-sitter-erlang: the WhatsApp/tree-sitter-erlang npm package was flagged as malware + // (GHSA-rphw-c8qj-jv84). The devDependency has been removed; grammars/*.wasm is gitignored + // so the Erlang WASM is not present in the repository. Erlang parsing is unavailable in + // the WASM engine until a clean replacement is integrated. To restore support: + // 1. npm install github:the-mikedavis/tree-sitter-erlang + // 2. Add this entry back to the grammars array + // 3. Run `npm run build:wasm`, validate the output + // 4. Add `!grammars/tree-sitter-erlang.wasm` to .gitignore and commit the WASM + // 5. Remove the temporary devDependency again { name: 'tree-sitter-solidity', pkg: 'tree-sitter-solidity', sub: null }, { name: 'tree-sitter-objc', pkg: 'tree-sitter-objc', sub: null }, { name: 'tree-sitter-cuda', pkg: 'tree-sitter-cuda', sub: null }, diff --git a/scripts/check-grammar-versions.mjs b/scripts/check-grammar-versions.mjs index 0c484006..ecd1a1c8 100644 --- a/scripts/check-grammar-versions.mjs +++ b/scripts/check-grammar-versions.mjs @@ -61,7 +61,6 @@ const GRAMMAR_NPM_PACKAGES = [ 'tree-sitter-cuda', 'tree-sitter-dart', 'tree-sitter-elixir', - 'tree-sitter-erlang', 'tree-sitter-fsharp', 'tree-sitter-gleam', 'tree-sitter-go', diff --git a/tests/benchmarks/regression-guard.test.ts b/tests/benchmarks/regression-guard.test.ts index 1b00479b..7ca1689d 100644 --- a/tests/benchmarks/regression-guard.test.ts +++ b/tests/benchmarks/regression-guard.test.ts @@ -311,6 +311,13 @@ const KNOWN_REGRESSIONS = new Set([ '3.11.2:Full build', '3.12.0:Full build', '3.12.0:1-file rebuild', + // tree-sitter-erlang devDependency removed (GHSA-rphw-c8qj-jv84 — malware). + // The erlang WASM is no longer built, so erlang resolution drops to 0%. + // These entries exempt the expected precision/recall drop on every build + // that follows the 3.12.0 baseline until a clean replacement grammar is + // integrated and a new baseline is captured. + '3.12.0:resolution erlang precision', + '3.12.0:resolution erlang recall', ]); /** diff --git a/tests/parsers/erlang.test.ts b/tests/parsers/erlang.test.ts index 6147d767..a7fc2cf5 100644 --- a/tests/parsers/erlang.test.ts +++ b/tests/parsers/erlang.test.ts @@ -1,11 +1,15 @@ import { beforeAll, describe, expect, it } from 'vitest'; import { createParsers, extractErlangSymbols } from '../../src/domain/parser.js'; +// tree-sitter-erlang devDependency was removed (GHSA-rphw-c8qj-jv84 — malware). +// When the WASM is not present, skip the suite rather than failing. describe('Erlang parser', () => { let parsers: any; + let erlangAvailable: boolean; beforeAll(async () => { parsers = await createParsers(); + erlangAvailable = !!parsers.get('erlang'); }); function parseErlang(code) { @@ -15,36 +19,42 @@ describe('Erlang parser', () => { return extractErlangSymbols(tree, 'test.erl'); } - it('extracts module declarations', () => { + it('extracts module declarations', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-module(mymodule).`); expect(symbols.definitions).toContainEqual( expect.objectContaining({ name: 'mymodule', kind: 'module' }), ); }); - it('extracts function definitions', () => { + it('extracts function definitions', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`greet(Name) -> io:format("Hello ~s~n", [Name]).`); expect(symbols.definitions).toContainEqual(expect.objectContaining({ kind: 'function' })); }); - it('extracts record definitions', () => { + it('extracts record definitions', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-record(person, {name, age}).`); expect(symbols.definitions).toContainEqual(expect.objectContaining({ kind: 'record' })); }); - it('extracts import attributes', () => { + it('extracts import attributes', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-import(lists, [map/2, filter/2]).`); expect(symbols.imports.length).toBeGreaterThanOrEqual(1); }); - it('extracts function calls', () => { + it('extracts function calls', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`start() -> io:format("Hello~n").`); expect(symbols.calls.length).toBeGreaterThanOrEqual(1); }); - it('keeps distinct arities for the same function name', () => { + it('keeps distinct arities for the same function name', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // Erlang overloads by arity; foo/1 and foo/2 are distinct definitions. const symbols = parseErlang(`foo(X) -> X. foo(X, Y) -> X + Y. @@ -55,7 +65,8 @@ foo(X, Y, Z) -> X + Y + Z.`); expect(arities).toEqual([1, 2, 3]); }); - it('counts complex pattern arguments as parameters', () => { + it('counts complex pattern arguments as parameters', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // Tuple, list, and binary pattern arguments must still count toward arity. const symbols = parseErlang(`handle({ok, X}, [H | T]) -> {X, H, T}.`); const f = symbols.definitions.find((d) => d.name === 'handle' && d.kind === 'function'); @@ -63,7 +74,8 @@ foo(X, Y, Z) -> X + Y + Z.`); expect(f?.children?.length).toBe(2); }); - it('extracts -type aliases', () => { + it('extracts -type aliases', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // Type-alias names are wrapped in a `type_name` node containing an atom in // the current grammar; the extractor handles both the wrapped form and a // direct atom fallback. @@ -73,7 +85,8 @@ foo(X, Y, Z) -> X + Y + Z.`); ); }); - it('extracts -opaque types', () => { + it('extracts -opaque types', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // -opaque uses the same `type_alias` node shape and must produce a type def. const symbols = parseErlang(`-opaque handle() :: reference().`); expect(symbols.definitions).toContainEqual( @@ -81,14 +94,16 @@ foo(X, Y, Z) -> X + Y + Z.`); ); }); - it('extracts -define macros as variables', () => { + it('extracts -define macros as variables', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-define(MAX_SIZE, 1024).`); expect(symbols.definitions).toContainEqual( expect.objectContaining({ name: 'MAX_SIZE', kind: 'variable' }), ); }); - it('extracts uppercase parametric macro names', () => { + it('extracts uppercase parametric macro names', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // Parametric macros wrap the name in `macro_lhs(name, args)`; the leading // child is the name (var for uppercase). const symbols = parseErlang(`-define(FOO(X), X + 1).`); @@ -97,7 +112,8 @@ foo(X, Y, Z) -> X + Y + Z.`); ); }); - it('extracts lowercase parametric macro names without mislabeling on argument vars', () => { + it('extracts lowercase parametric macro names without mislabeling on argument vars', (ctx) => { + if (!erlangAvailable) return ctx.skip(); // For lowercase parametric macros, macro_lhs children are // `atom("foo"), '(', var("X"), ')'`. The macro name must come from the // atom, not from `findChild(.., 'var')` which would land on the argument. @@ -109,14 +125,16 @@ foo(X, Y, Z) -> X + Y + Z.`); expect(symbols.definitions.some((d) => d.name === 'X')).toBe(false); }); - it('records -include with kind "include" so consumers resolve locally', () => { + it('records -include with kind "include" so consumers resolve locally', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-include("foo.hrl").`); const imp = symbols.imports.find((i) => i.source === 'foo.hrl'); expect(imp).toBeDefined(); expect(imp?.names).toEqual(['include']); }); - it('records -include_lib with kind "include_lib" so consumers resolve against OTP paths', () => { + it('records -include_lib with kind "include_lib" so consumers resolve against OTP paths', (ctx) => { + if (!erlangAvailable) return ctx.skip(); const symbols = parseErlang(`-include_lib("kernel/include/file.hrl").`); const imp = symbols.imports.find((i) => i.source === 'kernel/include/file.hrl'); expect(imp).toBeDefined();