oxlint-plugin-vue-sfc 0.1.2 → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-vue-sfc",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Vue SFC template rules for oxlint, as a JS plugin. Ports of eslint-plugin-vue rules oxlint has no native equivalent for.",
6
6
  "license": "MIT",
@@ -42,7 +42,8 @@
42
42
  "test": "node --test \"rules/*.spec.mjs\""
43
43
  },
44
44
  "dependencies": {
45
- "@vue/compiler-sfc": "^3.5.0"
45
+ "@vue/compiler-sfc": "^3.5.0",
46
+ "oxlint-vue-sfc-harness": "^0.1.0"
46
47
  },
47
48
  "devDependencies": {
48
49
  "oxc-parser": "^0.90.0",
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-child-content` (no native oxlint equivalent — oxc#15761).
@@ -4,7 +4,7 @@ import {
4
4
  parseSfc,
5
5
  reportAtFileOffset,
6
6
  walkTemplate,
7
- } from "../utils/vue-sfc.mjs";
7
+ } from "oxlint-vue-sfc-harness";
8
8
 
9
9
  /**
10
10
  * Replacement for `vue/no-dupe-v-else-if` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-duplicate-attributes` (no native oxlint equivalent — oxc#15761).
@@ -4,7 +4,7 @@ import {
4
4
  parseSfc,
5
5
  reportAtFileOffset,
6
6
  walkTemplate,
7
- } from "../utils/vue-sfc.mjs";
7
+ } from "oxlint-vue-sfc-harness";
8
8
 
9
9
  /**
10
10
  * Replacement for `vue/no-lone-template` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-mutating-props` (no native oxlint equivalent — oxc#15761).
@@ -1,4 +1,4 @@
1
- import { parseSfc, reportAtFileOffset } from "../utils/vue-sfc.mjs";
1
+ import { parseSfc, reportAtFileOffset } from "oxlint-vue-sfc-harness";
2
2
 
3
3
  /**
4
4
  * Replacement for `vue/no-parsing-error` (no native oxlint equivalent — oxc#15761).
@@ -1,4 +1,4 @@
1
- import { NODE_ELEMENT, NODE_TEXT, parseSfc, reportAtFileOffset } from "../utils/vue-sfc.mjs";
1
+ import { NODE_ELEMENT, NODE_TEXT, parseSfc, reportAtFileOffset } from "oxlint-vue-sfc-harness";
2
2
 
3
3
  /**
4
4
  * Replacement for `@intlify/vue-i18n/no-raw-text` (no native oxlint equivalent —
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-template-key` (no native oxlint equivalent — oxc#15761).
@@ -1,4 +1,4 @@
1
- import { NODE_ELEMENT, PROP_DIRECTIVE, parseSfc, reportAtFileOffset } from "../utils/vue-sfc.mjs";
1
+ import { NODE_ELEMENT, PROP_DIRECTIVE, parseSfc, reportAtFileOffset } from "oxlint-vue-sfc-harness";
2
2
  import {
3
3
  bindingsWithOffsets,
4
4
  declaredAlias,
@@ -4,7 +4,7 @@ import {
4
4
  parseSfc,
5
5
  reportAtFileOffset,
6
6
  walkTemplate,
7
- } from "../utils/vue-sfc.mjs";
7
+ } from "oxlint-vue-sfc-harness";
8
8
 
9
9
  /**
10
10
  * Replacement for `vue/no-textarea-mustache` (no native oxlint equivalent — oxc#15761).
@@ -1,4 +1,4 @@
1
- import { NODE_ELEMENT, PROP_DIRECTIVE, parseSfc, walkTemplate } from "../utils/vue-sfc.mjs";
1
+ import { NODE_ELEMENT, PROP_DIRECTIVE, parseSfc, walkTemplate } from "oxlint-vue-sfc-harness";
2
2
 
3
3
  /**
4
4
  * Replacement for `vue/no-unused-components` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
  import {
10
10
  bindingsWithOffsets,
11
11
  declaredAlias,
@@ -4,7 +4,7 @@ import {
4
4
  parseSfc,
5
5
  reportAtFileOffset,
6
6
  walkTemplate,
7
- } from "../utils/vue-sfc.mjs";
7
+ } from "oxlint-vue-sfc-harness";
8
8
 
9
9
  /**
10
10
  * Replacement for `vue/no-use-v-if-with-v-for` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-useless-template-attributes` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/no-v-text-v-html-on-component` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/require-component-is` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/require-explicit-emits` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/require-toggle-inside-transition` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/require-v-for-key` (no native oxlint equivalent — oxlint's 46
@@ -1,4 +1,4 @@
1
- import { eachTemplateExpression, parseSfc, reportAtFileOffset } from "../utils/vue-sfc.mjs";
1
+ import { eachTemplateExpression, parseSfc, reportAtFileOffset } from "oxlint-vue-sfc-harness";
2
2
 
3
3
  /**
4
4
  * Replacement for `vue/this-in-template` (no native oxlint equivalent — oxc#15761).
@@ -4,7 +4,7 @@ import {
4
4
  parseSfc,
5
5
  reportAtFileOffset,
6
6
  walkTemplate,
7
- } from "../utils/vue-sfc.mjs";
7
+ } from "oxlint-vue-sfc-harness";
8
8
 
9
9
  /**
10
10
  * Replacement for `vue/use-v-on-exact` (no native oxlint equivalent — oxc#15761).
@@ -5,7 +5,7 @@ import {
5
5
  parseSfc,
6
6
  reportAtFileOffset,
7
7
  walkTemplate,
8
- } from "../utils/vue-sfc.mjs";
8
+ } from "oxlint-vue-sfc-harness";
9
9
 
10
10
  /**
11
11
  * Replacement for `vue/v-on-event-hyphenation` (no native oxlint equivalent — oxc#15761).
@@ -2,7 +2,7 @@
2
2
  * Positioned extraction of the variables a Vue template DECLARES — `v-for` aliases and `v-slot`
3
3
  * destructuring — shared by vue-no-unused-vars and vue-no-template-shadow.
4
4
  *
5
- * `utils/vue-sfc.mjs` already has `extractBindingNames`, but it returns names only and bails on
5
+ * `oxlint-vue-sfc-harness` already has `extractBindingNames`, but it returns names only and bails on
6
6
  * any pattern containing `:` or `=`. Both rules here need the OFFSET of each binding (reports
7
7
  * anchor on the identifier itself) and need to handle renames and defaults, so this is a
8
8
  * separate, more capable extractor rather than a change to that one.
package/utils/vue-sfc.mjs DELETED
@@ -1,192 +0,0 @@
1
- import fs from "node:fs";
2
-
3
- import { parse } from "@vue/compiler-sfc";
4
-
5
- /**
6
- * Helpers for writing oxlint JS-plugin rules against the Vue TEMPLATE AST.
7
- *
8
- * oxlint parses only the script block of an SFC and gives JS-plugin rules a
9
- * script-only view: `context.sourceCode.text` is the script content and
10
- * line/column locations passed to `context.report()` are validated against the
11
- * script's line count (template lines are rejected with a RangeError).
12
- *
13
- * Two empirically verified escape hatches make template rules possible anyway:
14
- * 1. `fs.readFileSync(context.filename)` works inside the sidecar, so a rule
15
- * can parse the FULL SFC with @vue/compiler-sfc.
16
- * 2. `context.report({ node: { range: [start, end] } })` accepts raw
17
- * script-relative byte offsets WITHOUT line validation and maps them to
18
- * file positions by adding the script block's offset. A template offset is
19
- * therefore reachable only as `fileOffset - scriptStartOffset` when that is
20
- * >= 0 — i.e. when <script> precedes <template>.
21
- *
22
- * THIS REPO IS ENTIRELY THE OPPOSITE: every SFC is template-first (verified:
23
- * 1414 template-first, 0 script-first), so template offsets are always
24
- * NEGATIVE. oxlint rejects negative ranges (and rejects line/column that fall
25
- * outside the script block), so a template diagnostic CANNOT be rendered on
26
- * its real template line — the code frame always falls back to the script
27
- * block's first line (see reportAtFileOffset). This is an inherent limit of
28
- * oxlint's script-only JS-plugin view of .vue files, not a bug we can fix
29
- * here; we surface the true template line:column in the message text instead.
30
- */
31
-
32
- // One parse per file per lint run, shared across rules (module state lives for
33
- // the lifetime of oxlint's JS-plugin sidecar process).
34
- const cache = new Map();
35
-
36
- export function parseSfc(filename) {
37
- let entry = cache.get(filename);
38
- if (!entry) {
39
- const text = fs.readFileSync(filename, "utf8");
40
- const { descriptor, errors } = parse(text, { filename });
41
- entry = { text, descriptor, errors };
42
- cache.set(filename, entry);
43
- }
44
- return entry;
45
- }
46
-
47
- /** Script block content start offset in the file (script setup preferred). */
48
- export function scriptStartOffset(descriptor) {
49
- const block = descriptor.scriptSetup ?? descriptor.script;
50
- return block ? block.loc.start.offset : 0;
51
- }
52
-
53
- /**
54
- * Report a diagnostic at an absolute FILE offset range by translating it into
55
- * the script-relative range oxlint expects. Template positions BEFORE the
56
- * script block can't be represented as ranges (negative offsets) — the norm in
57
- * this template-first repo — so those fall back to the script block's first
58
- * line with the real template line:column PREPENDED to the message, so the
59
- * editor's problem list and the CLI still point you at the right place even
60
- * though the squiggle is stuck on the script block.
61
- */
62
- export function reportAtFileOffset(context, entry, fileStart, fileEnd, message) {
63
- const base = scriptStartOffset(entry.descriptor);
64
- const start = fileStart - base;
65
- if (start >= 0) {
66
- context.report({
67
- node: { type: "TemplateDiagnostic", range: [start, fileEnd - base] },
68
- message,
69
- });
70
- return;
71
- }
72
- const before = entry.text.slice(0, fileStart);
73
- const line = before.split("\n").length;
74
- const column = fileStart - (before.lastIndexOf("\n") + 1) + 1;
75
- context.report({
76
- node: { type: "TemplateDiagnostic", range: [0, 1] },
77
- message: `[template ${line}:${column}] ${message}`,
78
- });
79
- }
80
-
81
- /** Depth-first walk over @vue/compiler-dom template AST element nodes. */
82
- export function walkTemplate(node, visit) {
83
- if (!node) {
84
- return;
85
- }
86
- visit(node);
87
- if (Array.isArray(node.children)) {
88
- for (const child of node.children) {
89
- walkTemplate(child, visit);
90
- }
91
- }
92
- // v-if/v-else chains parsed standalone keep branches as siblings, but walk
93
- // branches too in case a transformed AST is ever passed in.
94
- if (Array.isArray(node.branches)) {
95
- for (const branch of node.branches) {
96
- walkTemplate(branch, visit);
97
- }
98
- }
99
- }
100
-
101
- /** @vue/compiler-dom NodeTypes used by rules (avoid importing internals). */
102
- export const NODE_ELEMENT = 1;
103
- export const NODE_TEXT = 2;
104
- export const NODE_INTERPOLATION = 5;
105
- export const PROP_ATTRIBUTE = 6;
106
- export const PROP_DIRECTIVE = 7;
107
- /** @vue/compiler-dom ElementTypes.COMPONENT */
108
- export const ELEMENT_COMPONENT = 1;
109
-
110
- /**
111
- * Visit every expression in the template: interpolations (directiveName null)
112
- * and directive expressions (directiveName e.g. "model", "bind", "if").
113
- */
114
- export function eachTemplateExpression(ast, visit) {
115
- walkTemplate(ast, (node) => {
116
- if (node.type === NODE_INTERPOLATION && node.content?.content) {
117
- visit(node.content, null);
118
- }
119
- if (node.type === NODE_ELEMENT) {
120
- for (const p of node.props ?? []) {
121
- if (p.type === PROP_DIRECTIVE && p.exp?.content) {
122
- visit(p.exp, p.name);
123
- }
124
- }
125
- }
126
- });
127
- }
128
-
129
- export function findDirective(el, name) {
130
- return el.props?.find((p) => p.type === PROP_DIRECTIVE && p.name === name);
131
- }
132
-
133
- export function findKeyProp(el) {
134
- return el.props?.find(
135
- (p) =>
136
- (p.type === PROP_ATTRIBUTE && p.name === "key") ||
137
- (p.type === PROP_DIRECTIVE &&
138
- p.name === "bind" &&
139
- p.arg?.type === 4 /* SIMPLE_EXPRESSION */ &&
140
- p.arg.content === "key"),
141
- );
142
- }
143
-
144
- /**
145
- * Extract variable names bound by a v-for / v-slot expression ("item",
146
- * "(item, index)", "{ row }"). Returns null for shapes with renames or
147
- * defaults (`{ a: b }`, `{ a = 1 }`) — identifying which side is the binding
148
- * needs a real parser, and a wrong guess produces false positives downstream.
149
- */
150
- export function extractBindingNames(exprText) {
151
- const left = exprText.split(/\s+(?:in|of)\s+/)[0] ?? exprText;
152
- if (/[:=]/.test(left)) {
153
- return null;
154
- }
155
- const names = [...left.matchAll(/[A-Za-z_$][\w$]*/g)].map((m) => m[0]);
156
- return names.length ? names : null;
157
- }
158
-
159
- /**
160
- * Collect every identifier bound by a JS destructuring pattern
161
- * (`a`, `{ a, b }`, `[a, b]`, `a = 1`, `...rest`) into `into`. Script-side rules use
162
- * this to know which names a declaration / parameter / catch clause introduces.
163
- */
164
- export const collectPatternNames = (pattern, into) => {
165
- if (!pattern) {
166
- return;
167
- }
168
- switch (pattern.type) {
169
- case "Identifier":
170
- into.add(pattern.name);
171
- break;
172
- case "ObjectPattern":
173
- for (const p of pattern.properties ?? []) {
174
- collectPatternNames(p.value ?? p.argument, into);
175
- }
176
- break;
177
- case "ArrayPattern":
178
- for (const el of pattern.elements ?? []) {
179
- collectPatternNames(el, into);
180
- }
181
- break;
182
- case "AssignmentPattern":
183
- collectPatternNames(pattern.left, into);
184
- break;
185
- case "RestElement":
186
- collectPatternNames(pattern.argument, into);
187
- break;
188
- default:
189
- break;
190
- }
191
- };
192
-