@etchteam/eslint-config 3.0.0 → 3.1.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/CHANGELOG.md +8 -17
- package/package.json +2 -1
- package/src/base.mjs +1 -0
- package/src/configs/environments/webc.mjs +17 -0
- package/src/configs/webc.mjs +18 -0
- package/src/index.mjs +1 -0
- package/src/plugins/webc-processor.mjs +289 -0
- package/src/types.d.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
|
-
## 3.
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
### BREAKING CHANGE
|
|
11
|
-
|
|
12
|
-
* Environment-specific plugins are no longer installed
|
|
13
|
-
automatically. Users must manually install the peer dependencies required
|
|
14
|
-
for their chosen config (e.g. `npm install angular-eslint` when using
|
|
15
|
-
`@etchteam/eslint-config/angular`).
|
|
16
|
-
|
|
17
|
-
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
1
|
+
## 3.1.0 (2026-04-02)
|
|
2
|
+
|
|
3
|
+
* Merge pull request #549 from etchteam/webc-support ([9b27bb3](https://github.com/etchteam/eslint/commit/9b27bb3)), closes [#549](https://github.com/etchteam/eslint/issues/549)
|
|
4
|
+
* refactor: address sonarcloud issues in webc processor ([069302f](https://github.com/etchteam/eslint/commit/069302f))
|
|
5
|
+
* refactor: deduplicate block extraction logic in webc processor ([2f98a07](https://github.com/etchteam/eslint/commit/2f98a07))
|
|
6
|
+
* feat: add WebC template linting support ([af7aad0](https://github.com/etchteam/eslint/commit/af7aad0))
|
|
7
|
+
* feat: enable curly rule to require braces on all if statements ([8b410d2](https://github.com/etchteam/eslint/commit/8b410d2))
|
|
8
|
+
* style: remove eslint-disable comments from webc processor ([77f7635](https://github.com/etchteam/eslint/commit/77f7635))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etchteam/eslint-config",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Etch's standard eslint config",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"./preact": "./src/configs/environments/preact.mjs",
|
|
17
17
|
"./angular": "./src/configs/environments/angular.mjs",
|
|
18
18
|
"./web-components": "./src/configs/environments/web-components.mjs",
|
|
19
|
+
"./webc": "./src/configs/environments/webc.mjs",
|
|
19
20
|
"./nestjs": "./src/configs/environments/nestjs.mjs",
|
|
20
21
|
"./package.json": "./package.json"
|
|
21
22
|
},
|
package/src/base.mjs
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import base from '../../base.mjs';
|
|
2
|
+
import json from '../json.mjs';
|
|
3
|
+
import storybook from '../storybook.mjs';
|
|
4
|
+
import webc from '../webc.mjs';
|
|
5
|
+
import yaml from '../yaml.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WebC ESLint configuration.
|
|
9
|
+
* Includes: base + JSON + YAML + Storybook + WebC processor
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```js
|
|
13
|
+
* import webc from '@etchteam/eslint-config/webc';
|
|
14
|
+
* export default webc;
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export default [...base, ...json, ...yaml, ...storybook, ...webc];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import webcPlugin from '../plugins/webc-processor.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WebC template linting configuration.
|
|
5
|
+
* Provides a processor for extracting and linting inline JavaScript in .webc files.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```js
|
|
9
|
+
* import { webc } from '@etchteam/eslint-config';
|
|
10
|
+
* export default [...base, ...webc];
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export default [
|
|
14
|
+
{
|
|
15
|
+
files: ['**/*.webc'],
|
|
16
|
+
processor: webcPlugin.processors.webc,
|
|
17
|
+
},
|
|
18
|
+
];
|
package/src/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ export { default as base } from './base.mjs';
|
|
|
9
9
|
export { default as json } from './configs/json.mjs';
|
|
10
10
|
export { default as react } from './configs/react.mjs';
|
|
11
11
|
export { default as storybook } from './configs/storybook.mjs';
|
|
12
|
+
export { default as webc } from './configs/webc.mjs';
|
|
12
13
|
export { default as yaml } from './configs/yaml.mjs';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block definitions for each type of inline JavaScript in WebC files.
|
|
3
|
+
* Each entry maps an opening tag pattern to its closing pattern and type resolver.
|
|
4
|
+
*/
|
|
5
|
+
const BLOCK_DEFS = [
|
|
6
|
+
{
|
|
7
|
+
open: /^(\s*)<script(\s[^>]*)?\s*>/i,
|
|
8
|
+
close: /<\/script\s*>/i,
|
|
9
|
+
getType: (line) => (/webc:setup/.test(line) ? 'script-setup' : 'script'),
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
open: /^(\s*)<template\s+webc:type=["'](js|render)["'](\s[^>]*)?\s*>/i,
|
|
13
|
+
close: /<\/template\s*>/i,
|
|
14
|
+
getType: (_line, match) => `template-${match[2]}`,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/** @type {Map<string, Array<{ startLine: number, charOffset: number, type: string }>>} */
|
|
19
|
+
const blockMetadata = new Map();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Try to match a line against all block definitions.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} line
|
|
25
|
+
* @returns {{ match: RegExpMatchArray, close: RegExp, type: string } | null}
|
|
26
|
+
*/
|
|
27
|
+
function matchOpenTag(line) {
|
|
28
|
+
for (const def of BLOCK_DEFS) {
|
|
29
|
+
const match = def.open.exec(line);
|
|
30
|
+
|
|
31
|
+
if (match) {
|
|
32
|
+
return { match, close: def.close, type: def.getType(line, match) };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Handle a line that opens a new block. Returns the block if it's a
|
|
41
|
+
* single-line block, or the parser state for a multi-line block.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} line
|
|
44
|
+
* @param {{ match: RegExpMatchArray, close: RegExp, type: string }} opened
|
|
45
|
+
* @param {number} lineIndex
|
|
46
|
+
* @param {number} currentCharOffset
|
|
47
|
+
* @returns {{ block: object } | { state: object }}
|
|
48
|
+
*/
|
|
49
|
+
function handleOpenTag(line, opened, lineIndex, currentCharOffset) {
|
|
50
|
+
const tagEnd = line.indexOf('>', opened.match.index) + 1;
|
|
51
|
+
const closeMatch = opened.close.exec(line);
|
|
52
|
+
|
|
53
|
+
if (closeMatch && closeMatch.index > opened.match.index) {
|
|
54
|
+
return {
|
|
55
|
+
block: {
|
|
56
|
+
code: line.slice(tagEnd, closeMatch.index),
|
|
57
|
+
startLine: lineIndex + 1,
|
|
58
|
+
charOffset: currentCharOffset + tagEnd,
|
|
59
|
+
type: opened.type,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const afterTag = line.slice(tagEnd);
|
|
65
|
+
const hasContentOnSameLine = afterTag.trim();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
state: {
|
|
69
|
+
closePattern: opened.close,
|
|
70
|
+
blockType: opened.type,
|
|
71
|
+
blockLines: hasContentOnSameLine ? [afterTag] : [],
|
|
72
|
+
blockStartLine: hasContentOnSameLine ? lineIndex + 1 : lineIndex + 2,
|
|
73
|
+
blockCharOffset: hasContentOnSameLine
|
|
74
|
+
? currentCharOffset + tagEnd
|
|
75
|
+
: currentCharOffset + line.length + 1,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handle a line while inside a block. Returns the completed block if the
|
|
82
|
+
* closing tag is found, or null to continue accumulating.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} line
|
|
85
|
+
* @param {RegExp} closePattern
|
|
86
|
+
* @param {string[]} blockLines
|
|
87
|
+
* @param {number} blockStartLine
|
|
88
|
+
* @param {number} blockCharOffset
|
|
89
|
+
* @param {string} blockType
|
|
90
|
+
* @returns {{ code: string, startLine: number, charOffset: number, type: string } | null}
|
|
91
|
+
*/
|
|
92
|
+
function handleBlockLine(
|
|
93
|
+
line,
|
|
94
|
+
closePattern,
|
|
95
|
+
blockLines,
|
|
96
|
+
blockStartLine,
|
|
97
|
+
blockCharOffset,
|
|
98
|
+
blockType,
|
|
99
|
+
) {
|
|
100
|
+
const closeMatch = closePattern.exec(line);
|
|
101
|
+
|
|
102
|
+
if (!closeMatch) {
|
|
103
|
+
blockLines.push(line);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const beforeClose = line.slice(0, closeMatch.index);
|
|
108
|
+
|
|
109
|
+
if (beforeClose.trim()) {
|
|
110
|
+
blockLines.push(beforeClose);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const code = blockLines.join('\n');
|
|
114
|
+
|
|
115
|
+
if (!code.trim()) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
code: code + '\n',
|
|
121
|
+
startLine: blockStartLine,
|
|
122
|
+
charOffset: blockCharOffset,
|
|
123
|
+
type: blockType,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extract JavaScript blocks from a WebC file.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} text - The full file content
|
|
131
|
+
* @returns {Array<{ code: string, startLine: number, charOffset: number, type: string }>}
|
|
132
|
+
*/
|
|
133
|
+
function extractBlocks(text) {
|
|
134
|
+
const lines = text.split('\n');
|
|
135
|
+
const blocks = [];
|
|
136
|
+
|
|
137
|
+
let insideBlock = false;
|
|
138
|
+
let closePattern = null;
|
|
139
|
+
let blockType = '';
|
|
140
|
+
let blockLines = [];
|
|
141
|
+
let blockStartLine = 0;
|
|
142
|
+
let blockCharOffset = 0;
|
|
143
|
+
let currentCharOffset = 0;
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < lines.length; i++) {
|
|
146
|
+
const line = lines[i];
|
|
147
|
+
|
|
148
|
+
if (insideBlock) {
|
|
149
|
+
const completed = handleBlockLine(
|
|
150
|
+
line,
|
|
151
|
+
closePattern,
|
|
152
|
+
blockLines,
|
|
153
|
+
blockStartLine,
|
|
154
|
+
blockCharOffset,
|
|
155
|
+
blockType,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
if (completed) {
|
|
159
|
+
blocks.push(completed);
|
|
160
|
+
insideBlock = false;
|
|
161
|
+
closePattern = null;
|
|
162
|
+
blockLines = [];
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const opened = matchOpenTag(line);
|
|
166
|
+
|
|
167
|
+
if (opened) {
|
|
168
|
+
const result = handleOpenTag(line, opened, i, currentCharOffset);
|
|
169
|
+
|
|
170
|
+
if (result.block) {
|
|
171
|
+
blocks.push(result.block);
|
|
172
|
+
} else {
|
|
173
|
+
insideBlock = true;
|
|
174
|
+
closePattern = result.state.closePattern;
|
|
175
|
+
blockType = result.state.blockType;
|
|
176
|
+
blockLines = result.state.blockLines;
|
|
177
|
+
blockStartLine = result.state.blockStartLine;
|
|
178
|
+
blockCharOffset = result.state.blockCharOffset;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
currentCharOffset += line.length + 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return blocks;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* @param {string} text
|
|
191
|
+
* @param {string} filename
|
|
192
|
+
* @returns {Array<{ text: string, filename: string }>}
|
|
193
|
+
*/
|
|
194
|
+
function preprocess(text, filename) {
|
|
195
|
+
const blocks = extractBlocks(text);
|
|
196
|
+
const metadata = [];
|
|
197
|
+
const result = [];
|
|
198
|
+
|
|
199
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
200
|
+
const block = blocks[i];
|
|
201
|
+
const virtualFilename = `${filename}/${i}.${block.type}.js`;
|
|
202
|
+
|
|
203
|
+
metadata.push({
|
|
204
|
+
startLine: block.startLine,
|
|
205
|
+
charOffset: block.charOffset,
|
|
206
|
+
type: block.type,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
result.push({
|
|
210
|
+
text: block.code,
|
|
211
|
+
filename: virtualFilename,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
blockMetadata.set(filename, metadata);
|
|
216
|
+
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {import('eslint').Linter.LintMessage} message
|
|
222
|
+
* @param {number} lineOffset
|
|
223
|
+
* @param {number} charOffset
|
|
224
|
+
* @returns {import('eslint').Linter.LintMessage}
|
|
225
|
+
*/
|
|
226
|
+
function adjustMessage(message, lineOffset, charOffset) {
|
|
227
|
+
return {
|
|
228
|
+
...message,
|
|
229
|
+
line: message.line + lineOffset,
|
|
230
|
+
endLine: message.endLine != null ? message.endLine + lineOffset : undefined,
|
|
231
|
+
fix: message.fix
|
|
232
|
+
? {
|
|
233
|
+
range: [
|
|
234
|
+
message.fix.range[0] + charOffset,
|
|
235
|
+
message.fix.range[1] + charOffset,
|
|
236
|
+
],
|
|
237
|
+
text: message.fix.text,
|
|
238
|
+
}
|
|
239
|
+
: undefined,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {Array<Array<import('eslint').Linter.LintMessage>>} messages
|
|
245
|
+
* @param {string} filename
|
|
246
|
+
* @returns {Array<import('eslint').Linter.LintMessage>}
|
|
247
|
+
*/
|
|
248
|
+
function postprocess(messages, filename) {
|
|
249
|
+
const metadata = blockMetadata.get(filename);
|
|
250
|
+
|
|
251
|
+
blockMetadata.delete(filename);
|
|
252
|
+
|
|
253
|
+
if (!metadata) {
|
|
254
|
+
return messages.flat();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const result = [];
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < messages.length; i++) {
|
|
260
|
+
const blockMessages = messages[i];
|
|
261
|
+
const meta = metadata[i];
|
|
262
|
+
|
|
263
|
+
if (meta) {
|
|
264
|
+
const lineOffset = meta.startLine - 1;
|
|
265
|
+
|
|
266
|
+
for (const message of blockMessages) {
|
|
267
|
+
result.push(adjustMessage(message, lineOffset, meta.charOffset));
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
result.push(...blockMessages);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export default {
|
|
278
|
+
meta: {
|
|
279
|
+
name: 'eslint-plugin-webc',
|
|
280
|
+
version: '1.0.0',
|
|
281
|
+
},
|
|
282
|
+
processors: {
|
|
283
|
+
webc: {
|
|
284
|
+
preprocess,
|
|
285
|
+
postprocess,
|
|
286
|
+
supportsAutofix: true,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
package/src/types.d.ts
CHANGED
|
@@ -52,6 +52,12 @@ export declare const preact: FlatConfigArray;
|
|
|
52
52
|
*/
|
|
53
53
|
export declare const angular: FlatConfigArray;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* WebC template linting configuration.
|
|
57
|
+
* Provides a processor for extracting and linting inline JavaScript in .webc files.
|
|
58
|
+
*/
|
|
59
|
+
export declare const webc: FlatConfigArray;
|
|
60
|
+
|
|
55
61
|
/**
|
|
56
62
|
* Web Components (Lit) ESLint configuration.
|
|
57
63
|
* Includes: base + JSON + YAML + Storybook + Lit rules
|