codemap-vue 0.2.0__tar.gz
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.
- codemap_vue-0.2.0/.gitignore +43 -0
- codemap_vue-0.2.0/PKG-INFO +118 -0
- codemap_vue-0.2.0/README.md +95 -0
- codemap_vue-0.2.0/pyproject.toml +41 -0
- codemap_vue-0.2.0/src/codemap_vue/__init__.py +13 -0
- codemap_vue-0.2.0/src/codemap_vue/indexer.py +293 -0
- codemap_vue-0.2.0/src/codemap_vue/sfc.py +102 -0
- codemap_vue-0.2.0/tests/__init__.py +0 -0
- codemap_vue-0.2.0/tests/test_indexer.py +208 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Build artifacts
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Test / coverage
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
.coverage.*
|
|
17
|
+
htmlcov/
|
|
18
|
+
coverage.xml
|
|
19
|
+
.tox/
|
|
20
|
+
.mypy_cache/
|
|
21
|
+
.ruff_cache/
|
|
22
|
+
.benchmarks/
|
|
23
|
+
|
|
24
|
+
# Virtualenv
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
env/
|
|
28
|
+
|
|
29
|
+
# uv / pdm lockfiles (commit uv.lock once we settle)
|
|
30
|
+
# uv.lock
|
|
31
|
+
|
|
32
|
+
# IDE
|
|
33
|
+
.idea/
|
|
34
|
+
.vscode/
|
|
35
|
+
*.swp
|
|
36
|
+
*.swo
|
|
37
|
+
|
|
38
|
+
# OS
|
|
39
|
+
.DS_Store
|
|
40
|
+
Thumbs.db
|
|
41
|
+
|
|
42
|
+
# CodeMap own index when dogfooding
|
|
43
|
+
.codemap/
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codemap-vue
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Vue SFC (.vue) indexer plugin for CodeMap (https://github.com/qxbyte/codemap)
|
|
5
|
+
Project-URL: Homepage, https://github.com/qxbyte/codemap
|
|
6
|
+
Author: CodeMap Contributors
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: codemap,indexer,sfc,tree-sitter,vue
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Programming Language :: JavaScript
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: codemap-core<0.3,>=0.2.0
|
|
15
|
+
Requires-Dist: tree-sitter-javascript>=0.23
|
|
16
|
+
Requires-Dist: tree-sitter>=0.25
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: tree-sitter-typescript>=0.23; extra == 'dev'
|
|
20
|
+
Provides-Extra: typescript
|
|
21
|
+
Requires-Dist: tree-sitter-typescript>=0.23; extra == 'typescript'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# codemap-vue
|
|
25
|
+
|
|
26
|
+
> A Vue Single File Component (`.vue`) indexer for
|
|
27
|
+
> [CodeMap](https://github.com/qxbyte/codemap), distributed as an
|
|
28
|
+
> independent PyPI package.
|
|
29
|
+
|
|
30
|
+
## What it captures
|
|
31
|
+
|
|
32
|
+
Backed by a small regex-based SFC scanner + tree-sitter:
|
|
33
|
+
|
|
34
|
+
| SFC block | Indexed | Backed by |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `<template>` | ❌ Ignored | — |
|
|
37
|
+
| `<script>` (`lang="js"` / default) | ✅ | `tree-sitter-javascript` (required) |
|
|
38
|
+
| `<script>` (`lang="jsx"`) | ✅ | `tree-sitter-javascript` (required) |
|
|
39
|
+
| `<script>` (`lang="ts"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
40
|
+
| `<script>` (`lang="tsx"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
41
|
+
| `<script setup>` (`lang="ts"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
42
|
+
| `<style>` | ❌ Ignored | — |
|
|
43
|
+
|
|
44
|
+
Inside the `<script>` block, the same symbol kinds as `codemap-javascript`
|
|
45
|
+
and `codemap-typescript` are surfaced:
|
|
46
|
+
|
|
47
|
+
| AST node | Symbol kind |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `function_declaration` | `function` |
|
|
50
|
+
| `class_declaration` | `class` |
|
|
51
|
+
| `method_definition` (inside class) | `method` |
|
|
52
|
+
| Top-level `const` / `let` / `var` | `variable` (with `extra.vue_block_lang`) |
|
|
53
|
+
|
|
54
|
+
Line numbers are translated back to the original `.vue` file coordinate
|
|
55
|
+
space, so `codemap get` jumps to the correct line even when the script
|
|
56
|
+
block starts hundreds of lines below `<template>`.
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# JavaScript-only Vue projects (no <script lang="ts">):
|
|
62
|
+
pip install codemap-vue
|
|
63
|
+
|
|
64
|
+
# Vue projects that use TypeScript in <script setup lang="ts"> (the
|
|
65
|
+
# Vue 3 default):
|
|
66
|
+
pip install "codemap-vue[typescript]"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`tree-sitter-typescript` is an optional dependency because some Vue
|
|
70
|
+
projects (Vue 2 codebases, JS-only sites) do not need it. When you index
|
|
71
|
+
a `.vue` file with `<script lang="ts">` and the extra is not installed,
|
|
72
|
+
the plugin emits a `VUE003` warning instead of crashing.
|
|
73
|
+
|
|
74
|
+
## Implementation notes
|
|
75
|
+
|
|
76
|
+
`tree-sitter-vue` is not currently published on PyPI. Rather than
|
|
77
|
+
bundle a hand-compiled grammar, this plugin uses a permissive regex
|
|
78
|
+
scanner (`codemap_vue.sfc.extract_script_blocks`) to locate every
|
|
79
|
+
top-level `<script>` block in the SFC, read its `lang=` attribute, and
|
|
80
|
+
slice out the inner bytes. The inner bytes are then handed to the
|
|
81
|
+
appropriate tree-sitter grammar.
|
|
82
|
+
|
|
83
|
+
The scanner is intentionally permissive about malformed input: a
|
|
84
|
+
missing `</script>` closing tag still produces a block that runs to
|
|
85
|
+
EOF, so indexing partial / in-progress files still yields useful
|
|
86
|
+
symbols.
|
|
87
|
+
|
|
88
|
+
## SymbolID encoding
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
scip-vue . . . src/components/UserList.vue/fetchUser().
|
|
92
|
+
└──────┘ └────────────────────────────────────────┘
|
|
93
|
+
scheme file → namespaces / type / method
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Tests
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install -e ".[dev]"
|
|
100
|
+
pytest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Limits / next steps
|
|
104
|
+
|
|
105
|
+
* `<template>` contents (directives, expressions, `v-on` handlers) are
|
|
106
|
+
not parsed. A future `codemap-vue` minor release could expose
|
|
107
|
+
declared template refs (`<input ref="emailRef" />`) and `defineProps`
|
|
108
|
+
/ `defineEmits` macros as symbols.
|
|
109
|
+
* `<script setup>` macros (`defineProps`, `defineExpose`, …) are
|
|
110
|
+
surfaced only when they appear as ordinary function calls inside a
|
|
111
|
+
variable declaration. Treating them as first-class component-shape
|
|
112
|
+
symbols is a v0.2.x improvement.
|
|
113
|
+
* Class-based component decorators (vue-class-component / vue-property-decorator)
|
|
114
|
+
are not yet captured.
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT — same as the host project.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# codemap-vue
|
|
2
|
+
|
|
3
|
+
> A Vue Single File Component (`.vue`) indexer for
|
|
4
|
+
> [CodeMap](https://github.com/qxbyte/codemap), distributed as an
|
|
5
|
+
> independent PyPI package.
|
|
6
|
+
|
|
7
|
+
## What it captures
|
|
8
|
+
|
|
9
|
+
Backed by a small regex-based SFC scanner + tree-sitter:
|
|
10
|
+
|
|
11
|
+
| SFC block | Indexed | Backed by |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `<template>` | ❌ Ignored | — |
|
|
14
|
+
| `<script>` (`lang="js"` / default) | ✅ | `tree-sitter-javascript` (required) |
|
|
15
|
+
| `<script>` (`lang="jsx"`) | ✅ | `tree-sitter-javascript` (required) |
|
|
16
|
+
| `<script>` (`lang="ts"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
17
|
+
| `<script>` (`lang="tsx"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
18
|
+
| `<script setup>` (`lang="ts"`) | ✅ (with `[typescript]` extra) | `tree-sitter-typescript` (optional) |
|
|
19
|
+
| `<style>` | ❌ Ignored | — |
|
|
20
|
+
|
|
21
|
+
Inside the `<script>` block, the same symbol kinds as `codemap-javascript`
|
|
22
|
+
and `codemap-typescript` are surfaced:
|
|
23
|
+
|
|
24
|
+
| AST node | Symbol kind |
|
|
25
|
+
|---|---|
|
|
26
|
+
| `function_declaration` | `function` |
|
|
27
|
+
| `class_declaration` | `class` |
|
|
28
|
+
| `method_definition` (inside class) | `method` |
|
|
29
|
+
| Top-level `const` / `let` / `var` | `variable` (with `extra.vue_block_lang`) |
|
|
30
|
+
|
|
31
|
+
Line numbers are translated back to the original `.vue` file coordinate
|
|
32
|
+
space, so `codemap get` jumps to the correct line even when the script
|
|
33
|
+
block starts hundreds of lines below `<template>`.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# JavaScript-only Vue projects (no <script lang="ts">):
|
|
39
|
+
pip install codemap-vue
|
|
40
|
+
|
|
41
|
+
# Vue projects that use TypeScript in <script setup lang="ts"> (the
|
|
42
|
+
# Vue 3 default):
|
|
43
|
+
pip install "codemap-vue[typescript]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`tree-sitter-typescript` is an optional dependency because some Vue
|
|
47
|
+
projects (Vue 2 codebases, JS-only sites) do not need it. When you index
|
|
48
|
+
a `.vue` file with `<script lang="ts">` and the extra is not installed,
|
|
49
|
+
the plugin emits a `VUE003` warning instead of crashing.
|
|
50
|
+
|
|
51
|
+
## Implementation notes
|
|
52
|
+
|
|
53
|
+
`tree-sitter-vue` is not currently published on PyPI. Rather than
|
|
54
|
+
bundle a hand-compiled grammar, this plugin uses a permissive regex
|
|
55
|
+
scanner (`codemap_vue.sfc.extract_script_blocks`) to locate every
|
|
56
|
+
top-level `<script>` block in the SFC, read its `lang=` attribute, and
|
|
57
|
+
slice out the inner bytes. The inner bytes are then handed to the
|
|
58
|
+
appropriate tree-sitter grammar.
|
|
59
|
+
|
|
60
|
+
The scanner is intentionally permissive about malformed input: a
|
|
61
|
+
missing `</script>` closing tag still produces a block that runs to
|
|
62
|
+
EOF, so indexing partial / in-progress files still yields useful
|
|
63
|
+
symbols.
|
|
64
|
+
|
|
65
|
+
## SymbolID encoding
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
scip-vue . . . src/components/UserList.vue/fetchUser().
|
|
69
|
+
└──────┘ └────────────────────────────────────────┘
|
|
70
|
+
scheme file → namespaces / type / method
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Tests
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install -e ".[dev]"
|
|
77
|
+
pytest
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Limits / next steps
|
|
81
|
+
|
|
82
|
+
* `<template>` contents (directives, expressions, `v-on` handlers) are
|
|
83
|
+
not parsed. A future `codemap-vue` minor release could expose
|
|
84
|
+
declared template refs (`<input ref="emailRef" />`) and `defineProps`
|
|
85
|
+
/ `defineEmits` macros as symbols.
|
|
86
|
+
* `<script setup>` macros (`defineProps`, `defineExpose`, …) are
|
|
87
|
+
surfaced only when they appear as ordinary function calls inside a
|
|
88
|
+
variable declaration. Treating them as first-class component-shape
|
|
89
|
+
symbols is a v0.2.x improvement.
|
|
90
|
+
* Class-based component decorators (vue-class-component / vue-property-decorator)
|
|
91
|
+
are not yet captured.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT — same as the host project.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codemap-vue"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Vue SFC (.vue) indexer plugin for CodeMap (https://github.com/qxbyte/codemap)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "CodeMap Contributors" }]
|
|
13
|
+
keywords = ["codemap", "vue", "sfc", "indexer", "tree-sitter"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Programming Language :: JavaScript",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Software Development",
|
|
19
|
+
]
|
|
20
|
+
# tree-sitter-vue is not on PyPI. The plugin extracts the <script> block
|
|
21
|
+
# via SFC top-level parsing, then dispatches to tree-sitter-javascript
|
|
22
|
+
# (always required) and tree-sitter-typescript (optional, only when the
|
|
23
|
+
# block declares lang="ts" / lang="tsx") for inner code.
|
|
24
|
+
dependencies = [
|
|
25
|
+
"codemap-core>=0.2.0,<0.3",
|
|
26
|
+
"tree-sitter>=0.25",
|
|
27
|
+
"tree-sitter-javascript>=0.23",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
typescript = ["tree-sitter-typescript>=0.23"]
|
|
32
|
+
dev = ["pytest>=8.0", "tree-sitter-typescript>=0.23"]
|
|
33
|
+
|
|
34
|
+
[project.entry-points."codemap.indexers"]
|
|
35
|
+
vue = "codemap_vue:VueIndexer"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/qxbyte/codemap"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/codemap_vue"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Vue SFC indexer plugin for CodeMap.
|
|
2
|
+
|
|
3
|
+
The entry-point group ``codemap.indexers`` discovers this class
|
|
4
|
+
automatically once ``codemap-vue`` is installed alongside the host
|
|
5
|
+
CodeMap CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from codemap_vue.indexer import VueIndexer
|
|
11
|
+
|
|
12
|
+
__all__ = ["VueIndexer"]
|
|
13
|
+
__version__ = "0.2.0a1"
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Vue SFC indexer.
|
|
2
|
+
|
|
3
|
+
Strategy
|
|
4
|
+
========
|
|
5
|
+
|
|
6
|
+
``.vue`` files mix three languages — HTML-like ``<template>``,
|
|
7
|
+
JS / TS in ``<script>``, and CSS / SCSS / Less in ``<style>``. Since
|
|
8
|
+
``tree-sitter-vue`` is not available on PyPI, this plugin:
|
|
9
|
+
|
|
10
|
+
1. Uses :mod:`codemap_vue.sfc` to locate every top-level ``<script>``
|
|
11
|
+
block in the file and read its ``lang=`` attribute.
|
|
12
|
+
2. Dispatches the block's bytes to the matching tree-sitter grammar
|
|
13
|
+
(``tree-sitter-javascript`` for JS / JSX, ``tree-sitter-typescript``
|
|
14
|
+
for TS / TSX). The TypeScript grammar is an optional dependency —
|
|
15
|
+
``.vue`` files that declare ``lang="ts"`` without it installed
|
|
16
|
+
produce a diagnostic, not a crash.
|
|
17
|
+
3. Walks the inner parse tree, collecting top-level functions, classes
|
|
18
|
+
(with methods), and module-level ``const`` / ``let`` / ``var``
|
|
19
|
+
declarations. Symbol line numbers are translated from script-local
|
|
20
|
+
to file-global coordinates so ``codemap get`` jumps to the right
|
|
21
|
+
line in the ``.vue`` source.
|
|
22
|
+
|
|
23
|
+
``<template>`` and ``<style>`` are intentionally ignored — they are
|
|
24
|
+
covered by neither tree-sitter-javascript nor tree-sitter-typescript,
|
|
25
|
+
and their template-only constructs are best served by a dedicated
|
|
26
|
+
``codemap-html`` plugin if such coverage is ever needed.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
from pathlib import Path, PurePosixPath
|
|
32
|
+
from typing import ClassVar
|
|
33
|
+
|
|
34
|
+
import tree_sitter
|
|
35
|
+
import tree_sitter_javascript
|
|
36
|
+
|
|
37
|
+
from codemap.core.models import Diagnostic, IndexResult, Range, Symbol
|
|
38
|
+
from codemap.core.symbol import Descriptor, DescriptorKind, SymbolID
|
|
39
|
+
from codemap.indexers.base import IndexContext
|
|
40
|
+
from codemap_vue.sfc import ScriptBlock, extract_script_blocks
|
|
41
|
+
|
|
42
|
+
SCHEME = "scip-vue"
|
|
43
|
+
LANG = "vue"
|
|
44
|
+
|
|
45
|
+
_JS_LANG = tree_sitter.Language(tree_sitter_javascript.language())
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
import tree_sitter_typescript as _ts_module
|
|
49
|
+
|
|
50
|
+
_TS_LANG: tree_sitter.Language | None = tree_sitter.Language(_ts_module.language_typescript())
|
|
51
|
+
_TSX_LANG: tree_sitter.Language | None = tree_sitter.Language(_ts_module.language_tsx())
|
|
52
|
+
except ImportError: # pragma: no cover - exercised in integration only
|
|
53
|
+
_TS_LANG = None
|
|
54
|
+
_TSX_LANG = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class VueIndexer:
|
|
58
|
+
name: ClassVar[str] = "vue"
|
|
59
|
+
version: ClassVar[str] = "0.2.0"
|
|
60
|
+
file_patterns: ClassVar[list[str]] = ["*.vue"]
|
|
61
|
+
languages: ClassVar[list[str]] = [LANG]
|
|
62
|
+
|
|
63
|
+
def supports(self, path: Path) -> bool:
|
|
64
|
+
return path.suffix == ".vue"
|
|
65
|
+
|
|
66
|
+
def index_file(
|
|
67
|
+
self,
|
|
68
|
+
path: Path,
|
|
69
|
+
source: bytes,
|
|
70
|
+
ctx: IndexContext,
|
|
71
|
+
) -> IndexResult:
|
|
72
|
+
try:
|
|
73
|
+
source.decode("utf-8")
|
|
74
|
+
except UnicodeDecodeError as exc:
|
|
75
|
+
return IndexResult(
|
|
76
|
+
diagnostics=[
|
|
77
|
+
Diagnostic(
|
|
78
|
+
severity="error",
|
|
79
|
+
file=ctx.relative_path,
|
|
80
|
+
code="VUE002",
|
|
81
|
+
message=f"not valid UTF-8: {exc}",
|
|
82
|
+
producer=self.name,
|
|
83
|
+
)
|
|
84
|
+
]
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
blocks = extract_script_blocks(source)
|
|
88
|
+
if not blocks:
|
|
89
|
+
return IndexResult()
|
|
90
|
+
|
|
91
|
+
symbols: list[Symbol] = []
|
|
92
|
+
diagnostics: list[Diagnostic] = []
|
|
93
|
+
|
|
94
|
+
for block in blocks:
|
|
95
|
+
lang_obj = _lang_for(block.lang)
|
|
96
|
+
if lang_obj is None:
|
|
97
|
+
diagnostics.append(
|
|
98
|
+
Diagnostic(
|
|
99
|
+
severity="warning",
|
|
100
|
+
file=ctx.relative_path,
|
|
101
|
+
range=Range(
|
|
102
|
+
start_line=block.content_start_line, end_line=block.content_start_line
|
|
103
|
+
),
|
|
104
|
+
code="VUE003",
|
|
105
|
+
message=(
|
|
106
|
+
f'<script lang="{block.lang}"> requires tree-sitter-typescript; '
|
|
107
|
+
f"install with: pip install codemap-vue[typescript]"
|
|
108
|
+
),
|
|
109
|
+
producer=self.name,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
parser = tree_sitter.Parser(lang_obj)
|
|
115
|
+
tree = parser.parse(block.content)
|
|
116
|
+
if tree.root_node.has_error:
|
|
117
|
+
diagnostics.append(
|
|
118
|
+
Diagnostic(
|
|
119
|
+
severity="warning",
|
|
120
|
+
file=ctx.relative_path,
|
|
121
|
+
range=Range(
|
|
122
|
+
start_line=block.content_start_line, end_line=block.content_start_line
|
|
123
|
+
),
|
|
124
|
+
code="VUE001",
|
|
125
|
+
message="tree-sitter reported parse errors inside <script>; symbols may be incomplete",
|
|
126
|
+
producer=self.name,
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
visitor = _ScriptVisitor(ctx.relative_path, block)
|
|
131
|
+
visitor.visit(tree.root_node)
|
|
132
|
+
symbols.extend(visitor.symbols)
|
|
133
|
+
|
|
134
|
+
return IndexResult(symbols=symbols, diagnostics=diagnostics)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _lang_for(script_lang: str) -> tree_sitter.Language | None:
|
|
138
|
+
if script_lang in {"js", "jsx"}:
|
|
139
|
+
return _JS_LANG
|
|
140
|
+
if script_lang == "ts":
|
|
141
|
+
return _TS_LANG
|
|
142
|
+
if script_lang == "tsx":
|
|
143
|
+
return _TSX_LANG
|
|
144
|
+
return _JS_LANG
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
# AST walking (mirrors codemap-typescript / codemap-javascript)
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class _ScriptVisitor:
|
|
153
|
+
"""Walks one ``<script>`` block's AST, translating positions."""
|
|
154
|
+
|
|
155
|
+
def __init__(self, relative_path: PurePosixPath, block: ScriptBlock) -> None:
|
|
156
|
+
self.relative_path = relative_path
|
|
157
|
+
self.block = block
|
|
158
|
+
self.symbols: list[Symbol] = []
|
|
159
|
+
self._class_stack: list[str] = []
|
|
160
|
+
|
|
161
|
+
def visit(self, node: tree_sitter.Node) -> None:
|
|
162
|
+
kind = node.type
|
|
163
|
+
if kind == "function_declaration":
|
|
164
|
+
self._visit_function(node, is_method=False)
|
|
165
|
+
return
|
|
166
|
+
if kind == "class_declaration":
|
|
167
|
+
self._visit_class(node)
|
|
168
|
+
return
|
|
169
|
+
if kind == "method_definition":
|
|
170
|
+
self._visit_function(node, is_method=True)
|
|
171
|
+
return
|
|
172
|
+
if kind in {"lexical_declaration", "variable_declaration"}:
|
|
173
|
+
self._visit_top_level_declaration(node)
|
|
174
|
+
for child in node.children:
|
|
175
|
+
self.visit(child)
|
|
176
|
+
|
|
177
|
+
def _visit_class(self, node: tree_sitter.Node) -> None:
|
|
178
|
+
name = _name_child_text(node)
|
|
179
|
+
if name is None:
|
|
180
|
+
return
|
|
181
|
+
sid = self._make_id(name, descriptor_kind=DescriptorKind.TYPE)
|
|
182
|
+
self.symbols.append(
|
|
183
|
+
Symbol(
|
|
184
|
+
id=sid,
|
|
185
|
+
kind="class",
|
|
186
|
+
language=LANG,
|
|
187
|
+
file=self.relative_path,
|
|
188
|
+
range=self._node_range(node),
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
self._class_stack.append(name)
|
|
192
|
+
try:
|
|
193
|
+
body = node.child_by_field_name("body")
|
|
194
|
+
if body is not None:
|
|
195
|
+
for child in body.children:
|
|
196
|
+
self.visit(child)
|
|
197
|
+
finally:
|
|
198
|
+
self._class_stack.pop()
|
|
199
|
+
|
|
200
|
+
def _visit_function(self, node: tree_sitter.Node, *, is_method: bool) -> None:
|
|
201
|
+
name = _name_child_text(node)
|
|
202
|
+
if name is None:
|
|
203
|
+
return
|
|
204
|
+
sid = self._make_id(name, descriptor_kind=DescriptorKind.METHOD)
|
|
205
|
+
kind: str = "method" if is_method or self._class_stack else "function"
|
|
206
|
+
signature = _function_signature(node, name)
|
|
207
|
+
self.symbols.append(
|
|
208
|
+
Symbol(
|
|
209
|
+
id=sid,
|
|
210
|
+
kind=kind, # type: ignore[arg-type]
|
|
211
|
+
language=LANG,
|
|
212
|
+
file=self.relative_path,
|
|
213
|
+
range=self._node_range(node),
|
|
214
|
+
signature=signature,
|
|
215
|
+
extra={"vue_block_lang": self.block.lang},
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
body = node.child_by_field_name("body")
|
|
219
|
+
if body is not None:
|
|
220
|
+
for child in body.children:
|
|
221
|
+
self.visit(child)
|
|
222
|
+
|
|
223
|
+
def _visit_top_level_declaration(self, node: tree_sitter.Node) -> None:
|
|
224
|
+
if self._class_stack:
|
|
225
|
+
return
|
|
226
|
+
for child in node.children:
|
|
227
|
+
if child.type != "variable_declarator":
|
|
228
|
+
continue
|
|
229
|
+
name_node = child.child_by_field_name("name")
|
|
230
|
+
if name_node is None or name_node.type != "identifier":
|
|
231
|
+
continue
|
|
232
|
+
name = name_node.text.decode("utf-8") if name_node.text else ""
|
|
233
|
+
if not name:
|
|
234
|
+
continue
|
|
235
|
+
sid = self._make_id(name, descriptor_kind=DescriptorKind.TERM)
|
|
236
|
+
self.symbols.append(
|
|
237
|
+
Symbol(
|
|
238
|
+
id=sid,
|
|
239
|
+
kind="variable",
|
|
240
|
+
language=LANG,
|
|
241
|
+
file=self.relative_path,
|
|
242
|
+
range=self._node_range(child),
|
|
243
|
+
extra={"vue_block_lang": self.block.lang},
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def _make_id(self, name: str, *, descriptor_kind: DescriptorKind) -> SymbolID:
|
|
248
|
+
descriptors = list(_path_namespaces(self.relative_path))
|
|
249
|
+
descriptors.extend(
|
|
250
|
+
Descriptor(name=cls, kind=DescriptorKind.TYPE) for cls in self._class_stack
|
|
251
|
+
)
|
|
252
|
+
descriptors.append(Descriptor(name=name, kind=descriptor_kind))
|
|
253
|
+
return SymbolID(scheme=SCHEME, descriptors=tuple(descriptors))
|
|
254
|
+
|
|
255
|
+
def _node_range(self, node: tree_sitter.Node) -> Range:
|
|
256
|
+
"""Convert script-local row to file-global row by adding block offset."""
|
|
257
|
+
start_row, start_col = node.start_point
|
|
258
|
+
end_row, end_col = node.end_point
|
|
259
|
+
# The block's content_start_line is 1-based and is the line where
|
|
260
|
+
# the content starts; node start_row is 0-based within the block.
|
|
261
|
+
offset = self.block.content_start_line - 1
|
|
262
|
+
return Range(
|
|
263
|
+
start_line=start_row + 1 + offset,
|
|
264
|
+
start_col=start_col,
|
|
265
|
+
end_line=max(end_row + 1 + offset, start_row + 1 + offset),
|
|
266
|
+
end_col=end_col,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# Pure helpers (shared shape with sibling indexers)
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _path_namespaces(path: PurePosixPath) -> list[Descriptor]:
|
|
276
|
+
return [Descriptor(name=part, kind=DescriptorKind.NAMESPACE) for part in path.parts]
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _name_child_text(node: tree_sitter.Node) -> str | None:
|
|
280
|
+
name_node = node.child_by_field_name("name")
|
|
281
|
+
if name_node is None or name_node.text is None:
|
|
282
|
+
return None
|
|
283
|
+
text = name_node.text.decode("utf-8").strip()
|
|
284
|
+
return text or None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _function_signature(node: tree_sitter.Node, name: str) -> str:
|
|
288
|
+
params = node.child_by_field_name("parameters")
|
|
289
|
+
params_text = ""
|
|
290
|
+
if params is not None and params.text is not None:
|
|
291
|
+
params_text = params.text.decode("utf-8")
|
|
292
|
+
prefix = "function" if node.type == "function_declaration" else ""
|
|
293
|
+
return (f"{prefix} {name}{params_text}").strip()
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Vue SFC (Single File Component) top-level extractor.
|
|
2
|
+
|
|
3
|
+
``tree-sitter-vue`` is not published on PyPI. The official Vue ecosystem
|
|
4
|
+
parses ``.vue`` files with a hand-written HTML scanner from
|
|
5
|
+
``@vue/compiler-sfc``; we mirror the minimum we need with a small
|
|
6
|
+
regex-driven scanner that locates the top-level ``<script>`` block (and
|
|
7
|
+
nothing else — ``<template>`` and ``<style>`` are deliberately ignored).
|
|
8
|
+
|
|
9
|
+
The scanner is intentionally permissive about malformed input: a missing
|
|
10
|
+
``</script>`` closing tag still produces a block that runs to EOF, so
|
|
11
|
+
indexing partial / in-progress files still yields useful symbols.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Literal
|
|
19
|
+
|
|
20
|
+
ScriptLang = Literal["ts", "tsx", "js", "jsx"]
|
|
21
|
+
|
|
22
|
+
_SCRIPT_OPEN_RE = re.compile(
|
|
23
|
+
rb"<script\b([^>]*)>", # captures attributes inside the open tag
|
|
24
|
+
re.IGNORECASE,
|
|
25
|
+
)
|
|
26
|
+
_SCRIPT_CLOSE_RE = re.compile(rb"</script\s*>", re.IGNORECASE)
|
|
27
|
+
_LANG_ATTR_RE = re.compile(
|
|
28
|
+
rb"""\blang\s*=\s*(?P<q>["'])(?P<lang>[^"']+)(?P=q)""",
|
|
29
|
+
re.IGNORECASE,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class ScriptBlock:
|
|
35
|
+
"""One ``<script>`` block extracted from a .vue file.
|
|
36
|
+
|
|
37
|
+
``lang`` is normalised to one of ``"ts" | "tsx" | "js" | "jsx"``;
|
|
38
|
+
defaults to ``"js"`` if no ``lang=`` attribute is present.
|
|
39
|
+
|
|
40
|
+
``content`` is the **bytes between the opening and closing tag**,
|
|
41
|
+
not including the tags themselves.
|
|
42
|
+
|
|
43
|
+
``content_start_offset`` is the byte offset within the original
|
|
44
|
+
file where ``content`` begins; it is used by the indexer to
|
|
45
|
+
translate AST line numbers back to source-file coordinates.
|
|
46
|
+
|
|
47
|
+
``content_start_line`` is the 1-based line number in the original
|
|
48
|
+
file where ``content`` begins.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
lang: ScriptLang
|
|
52
|
+
content: bytes
|
|
53
|
+
content_start_offset: int
|
|
54
|
+
content_start_line: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def extract_script_blocks(source: bytes) -> list[ScriptBlock]:
|
|
58
|
+
"""Locate every top-level ``<script>`` block in a Vue SFC.
|
|
59
|
+
|
|
60
|
+
Standard Vue 3 SFCs hold at most two ``<script>`` blocks: a normal
|
|
61
|
+
one and a ``<script setup>`` one. We return them in the order they
|
|
62
|
+
appear. Inner blocks (nested ``<script>`` inside a string literal
|
|
63
|
+
or template) are not unlikely, but for top-level SFC parsing the
|
|
64
|
+
naive scan is correct in practice.
|
|
65
|
+
"""
|
|
66
|
+
blocks: list[ScriptBlock] = []
|
|
67
|
+
cursor = 0
|
|
68
|
+
while cursor < len(source):
|
|
69
|
+
open_match = _SCRIPT_OPEN_RE.search(source, cursor)
|
|
70
|
+
if open_match is None:
|
|
71
|
+
break
|
|
72
|
+
attrs = open_match.group(1) or b""
|
|
73
|
+
content_start = open_match.end()
|
|
74
|
+
close_match = _SCRIPT_CLOSE_RE.search(source, content_start)
|
|
75
|
+
content_end = close_match.start() if close_match is not None else len(source)
|
|
76
|
+
content = source[content_start:content_end]
|
|
77
|
+
lang = _detect_lang(attrs)
|
|
78
|
+
blocks.append(
|
|
79
|
+
ScriptBlock(
|
|
80
|
+
lang=lang,
|
|
81
|
+
content=content,
|
|
82
|
+
content_start_offset=content_start,
|
|
83
|
+
content_start_line=source[:content_start].count(b"\n") + 1,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
cursor = close_match.end() if close_match is not None else content_end
|
|
87
|
+
return blocks
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _detect_lang(attrs: bytes) -> ScriptLang:
|
|
91
|
+
"""Detect ``lang=`` from the bytes inside ``<script ...>``."""
|
|
92
|
+
match = _LANG_ATTR_RE.search(attrs)
|
|
93
|
+
if match is None:
|
|
94
|
+
return "js"
|
|
95
|
+
raw = match.group("lang").decode("ascii", errors="replace").strip().lower()
|
|
96
|
+
if raw in {"ts", "typescript"}:
|
|
97
|
+
return "ts"
|
|
98
|
+
if raw == "tsx":
|
|
99
|
+
return "tsx"
|
|
100
|
+
if raw == "jsx":
|
|
101
|
+
return "jsx"
|
|
102
|
+
return "js"
|
|
File without changes
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Unit tests for the Vue SFC indexer plugin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
from pathlib import Path, PurePosixPath
|
|
7
|
+
|
|
8
|
+
from codemap_vue import VueIndexer
|
|
9
|
+
from codemap_vue.indexer import SCHEME
|
|
10
|
+
from codemap_vue.sfc import extract_script_blocks
|
|
11
|
+
|
|
12
|
+
from codemap.core.models import IndexResult
|
|
13
|
+
from codemap.indexers.base import IndexContext
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _index(source: str, *, path: str = "src/App.vue") -> IndexResult:
|
|
17
|
+
code = textwrap.dedent(source).lstrip("\n")
|
|
18
|
+
return VueIndexer().index_file(
|
|
19
|
+
Path(path),
|
|
20
|
+
code.encode("utf-8"),
|
|
21
|
+
IndexContext(
|
|
22
|
+
project_root=Path("/tmp/proj"),
|
|
23
|
+
relative_path=PurePosixPath(path),
|
|
24
|
+
language="vue",
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# SFC scanner (no parser dependency)
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_extract_script_blocks_basic() -> None:
|
|
35
|
+
src = b"<template><div/></template>\n<script>const a = 1;</script>"
|
|
36
|
+
blocks = extract_script_blocks(src)
|
|
37
|
+
assert len(blocks) == 1
|
|
38
|
+
assert blocks[0].lang == "js"
|
|
39
|
+
assert b"const a = 1" in blocks[0].content
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_extract_script_blocks_typescript_lang() -> None:
|
|
43
|
+
src = b'<script lang="ts">const x: number = 1;</script>'
|
|
44
|
+
blocks = extract_script_blocks(src)
|
|
45
|
+
assert len(blocks) == 1
|
|
46
|
+
assert blocks[0].lang == "ts"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_extract_script_blocks_setup_and_normal() -> None:
|
|
50
|
+
src = b'<script>export default {};</script>\n<script setup lang="ts">const n = 1;</script>'
|
|
51
|
+
blocks = extract_script_blocks(src)
|
|
52
|
+
assert len(blocks) == 2
|
|
53
|
+
assert [b.lang for b in blocks] == ["js", "ts"]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_extract_script_blocks_no_script_returns_empty() -> None:
|
|
57
|
+
src = b"<template><p>hi</p></template>\n<style>.x{}</style>"
|
|
58
|
+
blocks = extract_script_blocks(src)
|
|
59
|
+
assert blocks == []
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_extract_script_blocks_missing_close_runs_to_eof() -> None:
|
|
63
|
+
src = b"<script>const x = 1;\nconst y = 2;" # no </script>
|
|
64
|
+
blocks = extract_script_blocks(src)
|
|
65
|
+
assert len(blocks) == 1
|
|
66
|
+
assert b"const x" in blocks[0].content
|
|
67
|
+
assert b"const y" in blocks[0].content
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Indexer metadata
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_indexer_metadata() -> None:
|
|
76
|
+
ix = VueIndexer()
|
|
77
|
+
assert ix.name == "vue"
|
|
78
|
+
assert ix.languages == ["vue"]
|
|
79
|
+
assert ix.file_patterns == ["*.vue"]
|
|
80
|
+
assert ix.supports(Path("App.vue"))
|
|
81
|
+
assert not ix.supports(Path("App.js"))
|
|
82
|
+
assert not ix.supports(Path("App.ts"))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Indexer: SFC content
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_vue_with_only_template_produces_nothing() -> None:
|
|
91
|
+
r = _index(
|
|
92
|
+
"""
|
|
93
|
+
<template>
|
|
94
|
+
<div>hello</div>
|
|
95
|
+
</template>
|
|
96
|
+
"""
|
|
97
|
+
)
|
|
98
|
+
assert r.symbols == []
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_vue_script_js_top_level_function() -> None:
|
|
102
|
+
r = _index(
|
|
103
|
+
"""
|
|
104
|
+
<template><div/></template>
|
|
105
|
+
<script>
|
|
106
|
+
function greet(name) { return name; }
|
|
107
|
+
</script>
|
|
108
|
+
"""
|
|
109
|
+
)
|
|
110
|
+
assert len(r.symbols) == 1
|
|
111
|
+
assert r.symbols[0].kind == "function"
|
|
112
|
+
assert "greet" in str(r.symbols[0].id)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_vue_script_ts_top_level_class_and_method() -> None:
|
|
116
|
+
"""Requires the optional [typescript] extra."""
|
|
117
|
+
try:
|
|
118
|
+
import tree_sitter_typescript # noqa: F401
|
|
119
|
+
except ImportError:
|
|
120
|
+
return # extra not installed; skip
|
|
121
|
+
|
|
122
|
+
r = _index(
|
|
123
|
+
"""
|
|
124
|
+
<script lang="ts">
|
|
125
|
+
export class UserService {
|
|
126
|
+
login(name: string): boolean { return true; }
|
|
127
|
+
}
|
|
128
|
+
</script>
|
|
129
|
+
"""
|
|
130
|
+
)
|
|
131
|
+
kinds = sorted(s.kind for s in r.symbols)
|
|
132
|
+
assert "class" in kinds
|
|
133
|
+
assert "method" in kinds
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def test_vue_script_setup_typescript_const() -> None:
|
|
137
|
+
try:
|
|
138
|
+
import tree_sitter_typescript # noqa: F401
|
|
139
|
+
except ImportError:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
r = _index(
|
|
143
|
+
"""
|
|
144
|
+
<script setup lang="ts">
|
|
145
|
+
const message = 'hello';
|
|
146
|
+
</script>
|
|
147
|
+
"""
|
|
148
|
+
)
|
|
149
|
+
assert any(s.kind == "variable" and "message" in str(s.id) for s in r.symbols)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_vue_symbol_line_numbers_account_for_template_above() -> None:
|
|
153
|
+
"""Symbol line numbers must be in the .vue file coordinate space."""
|
|
154
|
+
r = _index(
|
|
155
|
+
"""
|
|
156
|
+
<template>
|
|
157
|
+
<div>line 2</div>
|
|
158
|
+
<div>line 3</div>
|
|
159
|
+
<div>line 4</div>
|
|
160
|
+
</template>
|
|
161
|
+
<script>
|
|
162
|
+
function below() { return 1; }
|
|
163
|
+
</script>
|
|
164
|
+
"""
|
|
165
|
+
)
|
|
166
|
+
fn = next(s for s in r.symbols if s.kind == "function")
|
|
167
|
+
# The script block starts on a later line; ensure the symbol line is
|
|
168
|
+
# not 1 (which is what a naive plug-the-script-into-an-empty-file
|
|
169
|
+
# implementation would produce).
|
|
170
|
+
assert fn.range is not None
|
|
171
|
+
assert fn.range.start_line >= 7
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_symbol_id_uses_path_namespaces() -> None:
|
|
175
|
+
r = _index(
|
|
176
|
+
"""
|
|
177
|
+
<script>
|
|
178
|
+
function f() {}
|
|
179
|
+
</script>
|
|
180
|
+
""",
|
|
181
|
+
path="pkg/sub/X.vue",
|
|
182
|
+
)
|
|
183
|
+
ids = [str(s.id) for s in r.symbols if s.kind == "function"]
|
|
184
|
+
assert any(i == "scip-vue . . . pkg/sub/X.vue/f()." for i in ids)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Diagnostics
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_invalid_utf8_yields_error_diagnostic() -> None:
|
|
193
|
+
ix = VueIndexer()
|
|
194
|
+
r = ix.index_file(
|
|
195
|
+
Path("bad.vue"),
|
|
196
|
+
b"\xff\xfe garbage",
|
|
197
|
+
IndexContext(
|
|
198
|
+
project_root=Path("/tmp/proj"),
|
|
199
|
+
relative_path=PurePosixPath("bad.vue"),
|
|
200
|
+
language="vue",
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
assert r.symbols == []
|
|
204
|
+
assert r.diagnostics[0].code == "VUE002"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_scheme_constant() -> None:
|
|
208
|
+
assert SCHEME == "scip-vue"
|