Merge branch 'main' into codex/prune-local-const-symbols

This commit is contained in:
Gergő Magyar 2026-06-07 06:32:05 +01:00 • committed by GitHub
commit 023c74c346
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 393 additions and 48 deletions

View file

@ -11,7 +11,7 @@
"license": "PolyForm-Noncommercial-1.0.0",
"dependencies": {
"@huggingface/transformers": "^4.1.0",
"@ladybugdb/core": "^0.16.1",
"@ladybugdb/core": "^0.17.0",
"@modelcontextprotocol/sdk": "^1.0.0",
"@scarf/scarf": "^1.4.0",
"cli-progress": "^3.12.0",
@ -1159,27 +1159,28 @@
}
},
"node_modules/@ladybugdb/core": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.16.1.tgz",
"integrity": "sha512-qwuEcR8CVMKb6tNDaHtq7Ux8hT/XbPC0db+vwutX6JxNAejyx7YomHKPSy9XAKURhYK8mezZe3UN8rf+xpHOjQ==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core/-/core-0.17.0.tgz",
"integrity": "sha512-fg7EGEJUj6H5JJLpU3iD5R0pAEc9u2OkU7ufX10krph9bCIhQyt/Tk6y/g4+x9zi/IHXegOjAVfSpWZp1gpoAA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"apache-arrow": "^21.1.0",
"cmake-js": "^8.0.0",
"node-addon-api": "^6.0.0"
},
"optionalDependencies": {
"@ladybugdb/core-darwin-arm64": "0.16.1",
"@ladybugdb/core-darwin-x64": "0.16.1",
"@ladybugdb/core-linux-arm64": "0.16.1",
"@ladybugdb/core-linux-x64": "0.16.1",
"@ladybugdb/core-win32-x64": "0.16.1"
"@ladybugdb/core-darwin-arm64": "0.17.0",
"@ladybugdb/core-darwin-x64": "0.17.0",
"@ladybugdb/core-linux-arm64": "0.17.0",
"@ladybugdb/core-linux-x64": "0.17.0",
"@ladybugdb/core-win32-x64": "0.17.0"
}
},
"node_modules/@ladybugdb/core-darwin-arm64": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.16.1.tgz",
"integrity": "sha512-Nl+Cf70rD+HaC9IBHv+oeUwqX9plghXD7PN9tyMzMohRVPvcGEbqWPB6YcdJa8rR7qRqCCbmaNMDen5wg4rY2w==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-arm64/-/core-darwin-arm64-0.17.0.tgz",
"integrity": "sha512-wghUBEmcQ9U10QOyOxXVQTZ6SHtkB8QV3sJHwCj2Dn5B/SsaB36kA4l2oKbgXpKDgjmSYiz3DfMs5yfDqen9UA==",
"cpu": [
"arm64"
],
@ -1190,9 +1191,9 @@
]
},
"node_modules/@ladybugdb/core-darwin-x64": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.16.1.tgz",
"integrity": "sha512-4eAjfimAAQRSmDfUUkGrl9OhefxcW1ziA9tl0eljBlGoUseE7dL02+RSqjGohYMcQ+lzuHAq1QWb0XRlMA8YTQ==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-darwin-x64/-/core-darwin-x64-0.17.0.tgz",
"integrity": "sha512-f0QRhmDY8NEMjAT3IbFELMEFxAnKq6trppn1vgAFIk9wTD/MKakfX/gtXOazpOZQHkwlfNgs+WSkJFfFvQ4JaQ==",
"cpu": [
"x64"
],
@ -1203,9 +1204,9 @@
]
},
"node_modules/@ladybugdb/core-linux-arm64": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.16.1.tgz",
"integrity": "sha512-zkctksev+hsPFrNxHHdq4lYK5OWdLhWfRdQzjzkgDyaHayHU6yCL2fgD6uPGQ8TRQ6/2DxMErb4p3FzGW85Ubw==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-arm64/-/core-linux-arm64-0.17.0.tgz",
"integrity": "sha512-TS9nbkvLJZt3Tgm6zzd/QuZmTiVoyQTPfbbwPej0VfcCVAfa1sDpZtoMlwse9EVHOrowQ0SorJVPg194lYVxdg==",
"cpu": [
"arm64"
],
@ -1216,9 +1217,9 @@
]
},
"node_modules/@ladybugdb/core-linux-x64": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.16.1.tgz",
"integrity": "sha512-5rAb9T5vif8WKhHwhobosu2/aiOwJkWb/ViybvUc5GFKunKl8VI6RmZQVeufT9zUzRktUwrxBrxblCxsnamXJw==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-linux-x64/-/core-linux-x64-0.17.0.tgz",
"integrity": "sha512-T/C0QKDoBCs8s/NQ2Udip8lZgJ8MzLqs2rRgreDd2dCP3aNnXu8eOBe10RSoRO5PiZZIZihXoz4Q8KMM6FtBGQ==",
"cpu": [
"x64"
],
@ -1229,9 +1230,9 @@
]
},
"node_modules/@ladybugdb/core-win32-x64": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.16.1.tgz",
"integrity": "sha512-ShOUTrIuZKQ63J95tcRJxKf1cvg8yi2FSYx9kMTSercc1FdQZPV+zxUN0myMq3MTWOl7xDxsVMmdp/t80O29UQ==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@ladybugdb/core-win32-x64/-/core-win32-x64-0.17.0.tgz",
"integrity": "sha512-XrQrbPD3h+MhP94jVu+4VgNnp8LKSskPll+/au+Ug3yqpXZ0We9lXX5+rW4NHuIYdAYy4iLheYz4OuozUS20qg==",
"cpu": [
"x64"
],
@ -1675,6 +1676,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/@swc/helpers": {
"version": "0.5.23",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
@ -1718,6 +1728,18 @@
"@types/node": "*"
}
},
"node_modules/@types/command-line-args": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz",
"integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==",
"license": "MIT"
},
"node_modules/@types/command-line-usage": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.4.tgz",
"integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==",
"license": "MIT"
},
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@ -2069,12 +2091,56 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/apache-arrow": {
"version": "21.1.0",
"resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-21.1.0.tgz",
"integrity": "sha512-kQrYLxhC+NTVVZ4CCzGF6L/uPVOzJmD1T3XgbiUnP7oTeVFOFgEUu6IKNwCDkpFoBVqDKQivlX4RUFqqnWFlEA==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.11",
"@types/command-line-args": "^5.2.3",
"@types/command-line-usage": "^5.0.4",
"@types/node": "^24.0.3",
"command-line-args": "^6.0.1",
"command-line-usage": "^7.0.1",
"flatbuffers": "^25.1.24",
"json-bignum": "^0.0.3",
"tslib": "^2.6.2"
},
"bin": {
"arrow2csv": "bin/arrow2csv.js"
}
},
"node_modules/apache-arrow/node_modules/@types/node": {
"version": "24.13.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.0.tgz",
"integrity": "sha512-5vtOqGQr4NJKeEzV441FcOi2MeG9UTWq9LqVLGneDdu4vlX17H8kQ2PA2UmNwCUGPVDj4oBjNhS7ReVEIWJJrg==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/apache-arrow/node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/array-back": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.3.tgz",
"integrity": "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@ -2199,6 +2265,37 @@
"node": ">=18"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
@ -2281,6 +2378,44 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT"
},
"node_modules/command-line-args": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.2.tgz",
"integrity": "sha512-AIjYVxrV9X752LmPDLbVYv8aMCuHPSLZJXEo2qo/xJfv+NYhaZ4sMSF01rM+gHPaMgvPM0l5D/F+Qx+i2WfSmQ==",
"license": "MIT",
"dependencies": {
"array-back": "^6.2.3",
"find-replace": "^5.0.2",
"lodash.camelcase": "^4.3.0",
"typical": "^7.3.0"
},
"engines": {
"node": ">=12.20"
},
"peerDependencies": {
"@75lb/nature": "latest"
},
"peerDependenciesMeta": {
"@75lb/nature": {
"optional": true
}
}
},
"node_modules/command-line-usage": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.4.tgz",
"integrity": "sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==",
"license": "MIT",
"dependencies": {
"array-back": "^6.2.2",
"chalk-template": "^0.4.0",
"table-layout": "^4.1.1",
"typical": "^7.3.0"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/commander": {
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
@ -2819,6 +2954,23 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/find-replace": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz",
"integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==",
"license": "MIT",
"engines": {
"node": ">=14"
},
"peerDependencies": {
"@75lb/nature": "latest"
},
"peerDependenciesMeta": {
"@75lb/nature": {
"optional": true
}
}
},
"node_modules/flatbuffers": {
"version": "25.9.23",
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz",
@ -3057,7 +3209,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -3296,6 +3447,14 @@
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-bignum": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz",
"integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
@ -3587,6 +3746,12 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@ -4693,7 +4858,6 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@ -4702,6 +4866,19 @@
"node": ">=8"
}
},
"node_modules/table-layout": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz",
"integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==",
"license": "MIT",
"dependencies": {
"array-back": "^6.2.2",
"wordwrapjs": "^5.1.0"
},
"engines": {
"node": ">=12.17"
}
},
"node_modules/tar": {
"version": "7.5.13",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
@ -5035,8 +5212,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"optional": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.22.4",
@ -5114,6 +5290,15 @@
"node": ">=14.17"
}
},
"node_modules/typical": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz",
"integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
@ -5366,6 +5551,15 @@
"node": ">=8"
}
},
"node_modules/wordwrapjs": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz",
"integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==",
"license": "MIT",
"engines": {
"node": ">=12.17"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View file

@ -55,7 +55,7 @@
},
"dependencies": {
"@huggingface/transformers": "^4.1.0",
"@ladybugdb/core": "^0.16.1",
"@ladybugdb/core": "^0.17.0",
"@modelcontextprotocol/sdk": "^1.0.0",
"@scarf/scarf": "^1.4.0",
"cli-progress": "^3.12.0",

View file

@ -22,6 +22,7 @@
import type { CaptureMatch } from 'gitnexus-shared';
import { extractVueScript } from '../../vue-sfc-extractor.js';
import { emitTsScopeCaptures } from '../typescript/captures.js';
import { emitJsScopeCaptures } from '../javascript/captures.js';
/**
* Emit scope captures for a Vue SFC.
@ -31,11 +32,11 @@ import { emitTsScopeCaptures } from '../typescript/captures.js';
* 1. **Full SFC content** (sequential path, <15 files): `sourceText`
* contains the whole `.vue` file with `<template>`, `<script>`, etc.
* `extractVueScript` extracts the script block and we delegate to
* `emitTsScopeCaptures` with that extracted content.
* `emitTsScopeCaptures` or `emitJsScopeCaptures` based on `lang`.
*
* 2. **Already-extracted script content** (worker-mode path, ≥15 files):
* the parse worker calls `extractVueScript` itself before calling
* `extractParsedFile`, so `sourceText` is already the bare TypeScript
* `extractParsedFile`, so `sourceText` is already the bare script
* text with no `<script>` tags. The caller marks this explicitly via
* `sourceMeta.sourceKind === 'pre-extracted-script'`.
*
@ -48,7 +49,7 @@ export function emitVueScopeCaptures(
sourceText: string,
filePath: string,
cachedTree?: unknown,
sourceMeta?: { sourceKind?: 'full-file' | 'pre-extracted-script' },
sourceMeta?: { sourceKind?: 'full-file' | 'pre-extracted-script'; setupLang?: string },
): readonly CaptureMatch[] {
// Vue resolver may include supporting TS/JS files in the same run to
// preserve cross-file import/type context for `.vue` callers. These are
@ -58,10 +59,19 @@ export function emitVueScopeCaptures(
}
if (sourceMeta?.sourceKind === 'pre-extracted-script') {
// Worker-mode path: the parse worker always uses TypeScript grammar for
// .vue files. Lang-based grammar selection is a sequential-path feature.
return emitTsScopeCaptures(sourceText, filePath, cachedTree);
}
const extracted = extractVueScript(sourceText);
if (extracted === null) return [];
// Select captures based on script lang attribute.
// Use TS grammar unless ALL blocks explicitly request JS/JSX.
// Mixed-lang: TS handles JS natively; JS grammar chokes on TS syntax.
if (extracted.lang === 'js' || extracted.lang === 'jsx') {
return emitJsScopeCaptures(extracted.scriptContent, filePath, cachedTree);
}
return emitTsScopeCaptures(extracted.scriptContent, filePath, cachedTree);
}

View file

@ -10,10 +10,13 @@
export interface VueScriptExtraction {
/** Extracted script content (TypeScript/JavaScript) */
scriptContent: string;
/** 0-based line number in the .vue file where the script content starts */
/** 1-based line number in the .vue file where the script content starts
* (used as offset from tree-sitter's 0-based row). */
lineOffset: number;
/** true if the primary block is <script setup> */
/** true if at least one block is <script setup> */
isSetup: boolean;
/** Value of the `lang` attribute on the extracted script block (e.g. "ts", "js", "tsx", "jsx", or "" for default). */
lang: string;
}
interface ScriptBlock {
@ -238,11 +241,9 @@ function parseScriptBlock(
/**
* Extract script content from a Vue SFC.
*
* When both <script> and <script setup> are present, returns only the
* <script setup> block (the dominant pattern — 94% of Vue files in real
* projects use setup). The <script> (non-setup) block typically contains
* only `defineOptions` or legacy option merges and is less important for
* the knowledge graph.
* When both <script> and <script setup> are present, the content of all
* blocks is combined (non-setup first, then setup) so the full script
* surface is available to the knowledge graph.
*/
export function extractVueScript(vueContent: string): VueScriptExtraction | null {
const blocks: ScriptBlock[] = [];
@ -257,14 +258,22 @@ export function extractVueScript(vueContent: string): VueScriptExtraction | null
if (blocks.length === 0) return null;
// Prefer <script setup> if present
const setupBlock = blocks.find((b) => b.isSetup);
const primary = setupBlock ?? blocks[0];
// Merge all blocks: non-setup first, then setup, so content order is stable.
const nonSetupBlocks = blocks.filter((b) => !b.isSetup);
const setupBlocks = blocks.filter((b) => b.isSetup);
const ordered = [...nonSetupBlocks, ...setupBlocks];
const combinedContent = ordered.map((b) => b.content).join('\n');
const lineOffset = ordered[0].lineOffset;
// Only use JavaScript grammar when ALL blocks are explicitly lang="js"
// or lang="jsx". If any block omits lang or uses ts/tsx, TypeScript wins.
const allBlocksJs = blocks.length > 0 && blocks.every((b) => b.lang === 'js' || b.lang === 'jsx');
const lang = allBlocksJs ? 'js' : '';
return {
scriptContent: primary.content,
lineOffset: primary.lineOffset,
isSetup: primary.isSetup,
scriptContent: combinedContent,
lineOffset,
isSetup: blocks.some((b) => b.isSetup),
lang,
};
}

View file

@ -0,0 +1,8 @@
// F44: Class expression — should produce a Class scope for the expression body
// On main: no @scope.class emitted → methods have no Class parent
// On this branch: (class) @scope.class emitted → methods get Class scope
export const instance = class {
greet(): string {
return 'hello';
}
};

View file

@ -0,0 +1,13 @@
<template>
<div>Hello</div>
</template>
<script>
function legacySetup() {
return { count: 0 };
}
</script>
<script setup lang="ts">
function setupInit() {
return 42;
}
</script>

View file

@ -0,0 +1,8 @@
<template>
<div>Hello</div>
</template>
<script lang="js">
function greet(name) {
return 'Hello ' + name;
}
</script>

View file

@ -473,3 +473,38 @@ describe('Vue cross-file composable and class resolution', () => {
expect(files.some((f) => f.endsWith('models.ts'))).toBe(true);
});
});
// ---------------------------------------------------------------------------
// F90 — dual-script merge (<script> + <script setup>)
// ---------------------------------------------------------------------------
describe('F90 — dual-script (<script> + <script setup>)', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(VUE_SCOPE_FIXTURES, 'vue-dual-script'), () => {});
}, 60000);
it('includes symbols from both the non-setup and setup blocks', () => {
const funcs = getNodesByLabel(result, 'Function');
expect(funcs).toContain('legacySetup');
expect(funcs).toContain('setupInit');
});
});
// ---------------------------------------------------------------------------
// F92 — JS-lang script block (<script lang="js">)
// ---------------------------------------------------------------------------
describe('F92 — JS-lang script block', () => {
let result: PipelineResult;
beforeAll(async () => {
result = await runPipelineFromRepo(path.join(VUE_SCOPE_FIXTURES, 'vue-js-lang'), () => {});
}, 60000);
it('parses <script lang="js"> content and finds functions', () => {
const funcs = getNodesByLabel(result, 'Function');
expect(funcs).toContain('greet');
});
});

View file

@ -45,13 +45,12 @@ export default {
expect(result!.scriptContent).toContain('export default');
});
it('prefers <script setup> when both blocks exist', () => {
it('combines both blocks when <script> and <script setup> both exist', () => {
const vue = `<script lang="ts">
export default {
inheritAttrs: false,
};
</script>
<script setup lang="ts">
import { ref } from 'vue';
const name = ref('test');
@ -61,9 +60,78 @@ const name = ref('test');
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
// F90: both blocks are combined
expect(result!.isSetup).toBe(true);
expect(result!.scriptContent).toContain("const name = ref('test')");
expect(result!.scriptContent).not.toContain('inheritAttrs');
expect(result!.scriptContent).toContain('inheritAttrs');
});
it('returns lang from the script block', () => {
const vue = `<script lang="js">
export default {};
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.lang).toBe('js');
});
it('returns empty lang when no lang attribute', () => {
const vue = `<script>
export default {};
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.lang).toBe('');
});
it('jsx lang triggers JS grammar (maps to lang=js)', () => {
const vue = `<script lang="jsx">
export default {};
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.lang).toBe('js');
});
it('ts lang returns empty (only js/jsx triggers JS grammar)', () => {
const vue = `<script lang="ts">
export default {};
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.lang).toBe('');
});
it('mixed js + ts blocks return empty lang (TypeScript wins)', () => {
const vue = `<script lang="js">
export default {};
</script>
<script setup lang="ts">
import { ref } from 'vue';
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.lang).toBe('');
});
it('isSetup is true when at least one block is setup', () => {
const vue = `<script lang="ts">
export default {};
</script>
<script setup lang="ts">
import { ref } from 'vue';
</script>
`;
const result = extractVueScript(vue);
expect(result).not.toBeNull();
expect(result!.isSetup).toBe(true);
// ts blocks — lang should be empty
expect(result!.lang).toBe('');
});
it('returns null for .vue files with no <script> block', () => {