eslint-plugin-better-stylelint 0.0.1
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/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +238 -0
- package/dist/worker.d.ts +5 -0
- package/dist/worker.js +42 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ice breaker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# eslint-plugin-better-stylelint
|
|
2
|
+
|
|
3
|
+
Bridge Stylelint diagnostics into ESLint for style files.
|
|
4
|
+
|
|
5
|
+
This package provides:
|
|
6
|
+
|
|
7
|
+
- ESLint processors for `*.css` and `*.scss`
|
|
8
|
+
- an ESLint rule for `.vue` files that forwards Stylelint diagnostics from
|
|
9
|
+
`<style>` blocks
|
|
10
|
+
- a bundled `synckit` worker so `stylelint.lint()` can be invoked through a
|
|
11
|
+
synchronous ESLint bridge
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
Use this when you want style issues to show up in the same ESLint diagnostic
|
|
16
|
+
stream as the rest of your project.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add -D eslint stylelint eslint-plugin-better-stylelint
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import betterStylelint from 'eslint-plugin-better-stylelint'
|
|
28
|
+
|
|
29
|
+
export default [
|
|
30
|
+
{
|
|
31
|
+
files: ['**/*.css'],
|
|
32
|
+
plugins: {
|
|
33
|
+
stylelint: betterStylelint,
|
|
34
|
+
},
|
|
35
|
+
processor: 'stylelint/css',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
files: ['**/*.scss'],
|
|
39
|
+
plugins: {
|
|
40
|
+
stylelint: betterStylelint,
|
|
41
|
+
},
|
|
42
|
+
processor: 'stylelint/scss',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
files: ['**/*.vue'],
|
|
46
|
+
plugins: {
|
|
47
|
+
stylelint: betterStylelint,
|
|
48
|
+
},
|
|
49
|
+
rules: {
|
|
50
|
+
'stylelint/stylelint': 'error',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The plugin relies on the consuming project's Stylelint configuration and
|
|
57
|
+
prefers the consuming project's `stylelint` installation. If the project does
|
|
58
|
+
not provide one, it falls back to the bundled `stylelint` dependency.
|
|
59
|
+
|
|
60
|
+
## Implementation Notes
|
|
61
|
+
|
|
62
|
+
- The synchronous bridge is implemented with `synckit`, not by spawning the
|
|
63
|
+
Stylelint CLI.
|
|
64
|
+
- The published package must include both `dist/index.js` and `dist/worker.js`.
|
|
65
|
+
- `src/core.ts` resolves the worker file by swapping `core.(ts|js)` to
|
|
66
|
+
`worker.(ts|js)`, so removing the worker build entry will break runtime
|
|
67
|
+
resolution.
|
|
68
|
+
|
|
69
|
+
## FAQ
|
|
70
|
+
|
|
71
|
+
### Does it automatically read `stylelint.config.js`?
|
|
72
|
+
|
|
73
|
+
Yes. The bridge calls `stylelint.lint({ code, codeFilename, cwd })` and lets
|
|
74
|
+
Stylelint handle config discovery. In practice that means common config entry
|
|
75
|
+
points such as `stylelint.config.js`, `stylelint.config.cjs`,
|
|
76
|
+
`stylelint.config.mjs`, `stylelint.config.ts`, and `package.json#stylelint`
|
|
77
|
+
are discovered by Stylelint itself.
|
|
78
|
+
|
|
79
|
+
### Does it support `extends`, `plugins`, and `customSyntax`?
|
|
80
|
+
|
|
81
|
+
Yes, as long as your Stylelint config can resolve them from the project. The
|
|
82
|
+
bridge does not bypass or replace Stylelint's normal config loading, so
|
|
83
|
+
`extends`, plugin rules, and `customSyntax` continue to work the same way they
|
|
84
|
+
would when you run Stylelint directly.
|
|
85
|
+
|
|
86
|
+
### Which `stylelint` installation does it use?
|
|
87
|
+
|
|
88
|
+
It prefers the consuming project's own `stylelint` installation. If the
|
|
89
|
+
project does not provide one, the bridge falls back to the bundled `stylelint`
|
|
90
|
+
dependency shipped with `eslint-plugin-better-stylelint`.
|
|
91
|
+
|
|
92
|
+
For the most predictable behavior, install `stylelint` in the project root so
|
|
93
|
+
your ESLint bridge, Stylelint CLI, and editor integrations all use the same
|
|
94
|
+
version.
|
|
95
|
+
|
|
96
|
+
### Will this affect IDE plugins?
|
|
97
|
+
|
|
98
|
+
Usually the ESLint extension works as expected, because the bridge runs inside
|
|
99
|
+
ESLint. The main thing to watch is consistency:
|
|
100
|
+
|
|
101
|
+
- If the project has its own `stylelint`, the ESLint bridge and Stylelint IDE
|
|
102
|
+
extension should stay aligned.
|
|
103
|
+
- If the project does not have its own `stylelint`, the ESLint bridge can still
|
|
104
|
+
work via the bundled fallback, but a standalone Stylelint IDE extension may
|
|
105
|
+
not behave exactly the same way.
|
|
106
|
+
|
|
107
|
+
### How does it handle Vue SFCs?
|
|
108
|
+
|
|
109
|
+
Each `<style>` block in a `.vue` file is linted separately. The bridge:
|
|
110
|
+
|
|
111
|
+
- extracts the block content instead of linting the whole SFC as one string
|
|
112
|
+
- generates a virtual filename based on the block `lang`, such as `css` or
|
|
113
|
+
`scss`
|
|
114
|
+
- maps Stylelint diagnostics back to the original `.vue` line and column
|
|
115
|
+
- includes block context such as `scoped`, `module`, and `lang` in the message
|
|
116
|
+
when that context helps identify the source block
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
interface BetterStylelintMessage {
|
|
3
|
+
ruleId: string;
|
|
4
|
+
message: string;
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
endLine?: number;
|
|
8
|
+
endColumn?: number;
|
|
9
|
+
severity: 1 | 2;
|
|
10
|
+
fatal?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface BetterStylelintProcessor {
|
|
13
|
+
meta?: {
|
|
14
|
+
name?: string;
|
|
15
|
+
version?: string;
|
|
16
|
+
};
|
|
17
|
+
preprocess: (text: string, filename: string) => string[];
|
|
18
|
+
postprocess: (messages: unknown[][], filename: string) => BetterStylelintMessage[];
|
|
19
|
+
supportsAutofix?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface BetterStylelintRuleOptions {
|
|
22
|
+
cwd?: string;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/processor.d.ts
|
|
26
|
+
declare const cssProcessor: BetterStylelintProcessor;
|
|
27
|
+
declare const scssProcessor: BetterStylelintProcessor;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/rule.d.ts
|
|
30
|
+
interface RuleContext {
|
|
31
|
+
filename: string;
|
|
32
|
+
options: BetterStylelintRuleOptions[];
|
|
33
|
+
report: (descriptor: {
|
|
34
|
+
loc: {
|
|
35
|
+
start: {
|
|
36
|
+
line: number;
|
|
37
|
+
column: number;
|
|
38
|
+
};
|
|
39
|
+
end?: {
|
|
40
|
+
line: number;
|
|
41
|
+
column: number;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
message: string;
|
|
45
|
+
}) => void;
|
|
46
|
+
sourceCode: {
|
|
47
|
+
text: string;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
declare const lintRule: {
|
|
51
|
+
meta: {
|
|
52
|
+
type: string;
|
|
53
|
+
docs: {
|
|
54
|
+
description: string;
|
|
55
|
+
};
|
|
56
|
+
schema: {
|
|
57
|
+
type: string;
|
|
58
|
+
additionalProperties: boolean;
|
|
59
|
+
properties: {
|
|
60
|
+
cwd: {
|
|
61
|
+
type: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}[];
|
|
65
|
+
};
|
|
66
|
+
create(context: RuleContext): {
|
|
67
|
+
Program(): void;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/core.d.ts
|
|
72
|
+
declare function runStylelintSync(code: string, filename: string, cwd?: string): BetterStylelintMessage[];
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/index.d.ts
|
|
75
|
+
interface BetterStylelintPlugin {
|
|
76
|
+
meta: {
|
|
77
|
+
name: string;
|
|
78
|
+
version: string;
|
|
79
|
+
};
|
|
80
|
+
processors: {
|
|
81
|
+
css: typeof cssProcessor;
|
|
82
|
+
scss: typeof scssProcessor;
|
|
83
|
+
};
|
|
84
|
+
rules: {
|
|
85
|
+
stylelint: typeof lintRule;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
declare const plugin: BetterStylelintPlugin;
|
|
89
|
+
//#endregion
|
|
90
|
+
export { type BetterStylelintMessage, type BetterStylelintProcessor, type BetterStylelintRuleOptions, cssProcessor, plugin as default, lintRule, runStylelintSync, scssProcessor };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createSyncFn } from "synckit";
|
|
5
|
+
//#region src/core.ts
|
|
6
|
+
const MAX_CACHE_ENTRIES = 100;
|
|
7
|
+
const CORE_TS_PATTERN = /core\.ts$/u;
|
|
8
|
+
const CORE_JS_PATTERN = /core\.js$/u;
|
|
9
|
+
const resultCache = /* @__PURE__ */ new Map();
|
|
10
|
+
let runStylelintWorker;
|
|
11
|
+
function normalizeStylelintResults(result) {
|
|
12
|
+
return [...result.warnings ?? [], ...result.parseErrors ?? []].map((warning) => ({
|
|
13
|
+
ruleId: warning.rule ?? "stylelint",
|
|
14
|
+
message: warning.text,
|
|
15
|
+
line: warning.line ?? 1,
|
|
16
|
+
column: warning.column ?? 1,
|
|
17
|
+
...warning.endLine !== void 0 ? { endLine: warning.endLine } : {},
|
|
18
|
+
...warning.endColumn !== void 0 ? { endColumn: warning.endColumn } : {},
|
|
19
|
+
severity: warning.severity === "warning" ? 1 : 2,
|
|
20
|
+
...warning.rule === void 0 ? { fatal: true } : {}
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
function createBridgeError(message) {
|
|
24
|
+
return [{
|
|
25
|
+
ruleId: "stylelint/bridge",
|
|
26
|
+
message,
|
|
27
|
+
line: 1,
|
|
28
|
+
column: 1,
|
|
29
|
+
severity: 2,
|
|
30
|
+
fatal: true
|
|
31
|
+
}];
|
|
32
|
+
}
|
|
33
|
+
function cloneMessages(messages) {
|
|
34
|
+
return messages.map((message) => ({ ...message }));
|
|
35
|
+
}
|
|
36
|
+
function createCacheKey(code, filename, cwd) {
|
|
37
|
+
return `${cwd}\0${filename}\0${createHash("sha1").update(code).digest("hex")}`;
|
|
38
|
+
}
|
|
39
|
+
function getCachedMessages(cacheKey) {
|
|
40
|
+
const cached = resultCache.get(cacheKey);
|
|
41
|
+
if (!cached) return;
|
|
42
|
+
resultCache.delete(cacheKey);
|
|
43
|
+
resultCache.set(cacheKey, cached);
|
|
44
|
+
return cloneMessages(cached);
|
|
45
|
+
}
|
|
46
|
+
function setCachedMessages(cacheKey, messages) {
|
|
47
|
+
if (resultCache.has(cacheKey)) resultCache.delete(cacheKey);
|
|
48
|
+
resultCache.set(cacheKey, messages);
|
|
49
|
+
while (resultCache.size > MAX_CACHE_ENTRIES) {
|
|
50
|
+
const oldestKey = resultCache.keys().next().value;
|
|
51
|
+
if (oldestKey === void 0) break;
|
|
52
|
+
resultCache.delete(oldestKey);
|
|
53
|
+
}
|
|
54
|
+
return cloneMessages(messages);
|
|
55
|
+
}
|
|
56
|
+
function resolveWorkerPath() {
|
|
57
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
58
|
+
if (currentFilePath.endsWith(".ts")) return currentFilePath.replace(CORE_TS_PATTERN, "worker.ts");
|
|
59
|
+
return currentFilePath.replace(CORE_JS_PATTERN, "worker.js");
|
|
60
|
+
}
|
|
61
|
+
function getRunStylelintWorker() {
|
|
62
|
+
runStylelintWorker ??= createSyncFn(resolveWorkerPath());
|
|
63
|
+
return runStylelintWorker;
|
|
64
|
+
}
|
|
65
|
+
function runStylelintSync(code, filename, cwd = path.dirname(filename)) {
|
|
66
|
+
const cacheKey = createCacheKey(code, filename, cwd);
|
|
67
|
+
const cached = getCachedMessages(cacheKey);
|
|
68
|
+
if (cached) return cached;
|
|
69
|
+
const response = getRunStylelintWorker()(code, filename, cwd);
|
|
70
|
+
if (!response.ok) return setCachedMessages(cacheKey, createBridgeError(response.error));
|
|
71
|
+
return setCachedMessages(cacheKey, normalizeStylelintResults(response.result));
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/processor.ts
|
|
75
|
+
const sourceCache = /* @__PURE__ */ new Map();
|
|
76
|
+
function createProcessor() {
|
|
77
|
+
return {
|
|
78
|
+
meta: {
|
|
79
|
+
name: "eslint-plugin-better-stylelint/processor",
|
|
80
|
+
version: "0.0.1"
|
|
81
|
+
},
|
|
82
|
+
preprocess(text, filename) {
|
|
83
|
+
sourceCache.set(filename, text);
|
|
84
|
+
return ["/* eslint-plugin-better-stylelint */"];
|
|
85
|
+
},
|
|
86
|
+
postprocess(_messages, filename) {
|
|
87
|
+
const source = sourceCache.get(filename) ?? "";
|
|
88
|
+
sourceCache.delete(filename);
|
|
89
|
+
return runStylelintSync(source, filename);
|
|
90
|
+
},
|
|
91
|
+
supportsAutofix: false
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const cssProcessor = createProcessor();
|
|
95
|
+
const scssProcessor = createProcessor();
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/rule.ts
|
|
98
|
+
const VUE_STYLE_BLOCK_PATTERN = /<style\b[^>]*>[\s\S]*?<\/style>/giu;
|
|
99
|
+
const VUE_STYLE_OPENING_TAG_PATTERN = /^<style\b([^>]*)>/iu;
|
|
100
|
+
const HTML_ATTRIBUTE_PATTERN = /([:@\w-]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/gu;
|
|
101
|
+
function getColumn(value) {
|
|
102
|
+
return Math.max(0, (value ?? 1) - 1);
|
|
103
|
+
}
|
|
104
|
+
function getLineColumnAtOffset(source, offset) {
|
|
105
|
+
let line = 1;
|
|
106
|
+
let column = 1;
|
|
107
|
+
for (let index = 0; index < offset; index += 1) {
|
|
108
|
+
if (source[index] === "\n") {
|
|
109
|
+
line += 1;
|
|
110
|
+
column = 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
column += 1;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
line,
|
|
117
|
+
column
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function parseStyleAttributes(code) {
|
|
121
|
+
const rawAttributes = code.match(VUE_STYLE_OPENING_TAG_PATTERN)?.[1];
|
|
122
|
+
if (!rawAttributes) return {};
|
|
123
|
+
const attributes = {};
|
|
124
|
+
for (const match of rawAttributes.matchAll(HTML_ATTRIBUTE_PATTERN)) {
|
|
125
|
+
const name = match[1];
|
|
126
|
+
if (!name) continue;
|
|
127
|
+
const value = match[2] ?? match[3] ?? match[4];
|
|
128
|
+
attributes[name] = value === void 0 ? true : value;
|
|
129
|
+
}
|
|
130
|
+
return attributes;
|
|
131
|
+
}
|
|
132
|
+
function createVueStyleBlockLabel(index, attributes, blockCount) {
|
|
133
|
+
const segments = blockCount > 1 ? [`style#${index + 1}`] : ["style"];
|
|
134
|
+
if (attributes["scoped"]) segments.push("scoped");
|
|
135
|
+
if (attributes["module"]) {
|
|
136
|
+
const moduleValue = attributes["module"];
|
|
137
|
+
segments.push(moduleValue === true ? "module" : `module=${moduleValue}`);
|
|
138
|
+
}
|
|
139
|
+
if (typeof attributes["lang"] === "string") segments.push(`lang=${attributes["lang"]}`);
|
|
140
|
+
return segments.length > 1 ? segments.join(" ") : void 0;
|
|
141
|
+
}
|
|
142
|
+
function normalizeStyleLang(attributes) {
|
|
143
|
+
const lang = attributes["lang"];
|
|
144
|
+
if (typeof lang !== "string" || !lang.trim()) return "css";
|
|
145
|
+
return lang.trim().toLowerCase();
|
|
146
|
+
}
|
|
147
|
+
function createVueStyleVirtualFilename(filename, index, attributes) {
|
|
148
|
+
return `${filename}__style_${index}.${normalizeStyleLang(attributes)}`;
|
|
149
|
+
}
|
|
150
|
+
function extractVueStyleBlocks(source, filename = "Component.vue") {
|
|
151
|
+
const matches = [...source.matchAll(VUE_STYLE_BLOCK_PATTERN)];
|
|
152
|
+
const blocks = [];
|
|
153
|
+
for (const [matchIndex, match] of matches.entries()) {
|
|
154
|
+
const code = match[0];
|
|
155
|
+
const matchOffset = match.index;
|
|
156
|
+
if (code === void 0 || matchOffset === void 0) continue;
|
|
157
|
+
const attributes = parseStyleAttributes(code);
|
|
158
|
+
const label = createVueStyleBlockLabel(matchIndex, attributes, matches.length);
|
|
159
|
+
const contentStartOffset = matchOffset + (code.match(VUE_STYLE_OPENING_TAG_PATTERN)?.[0] ?? "<style>").length;
|
|
160
|
+
const contentEndOffset = matchOffset + code.length - 8;
|
|
161
|
+
const content = source.slice(contentStartOffset, contentEndOffset);
|
|
162
|
+
const location = getLineColumnAtOffset(source, contentStartOffset);
|
|
163
|
+
blocks.push({
|
|
164
|
+
attributes,
|
|
165
|
+
code,
|
|
166
|
+
content,
|
|
167
|
+
index: matchIndex,
|
|
168
|
+
...label !== void 0 ? { label } : {},
|
|
169
|
+
startLine: location.line,
|
|
170
|
+
startColumn: location.column,
|
|
171
|
+
virtualFilename: createVueStyleVirtualFilename(filename, matchIndex, attributes)
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return blocks;
|
|
175
|
+
}
|
|
176
|
+
function mapDiagnosticToVueFile(diagnostic, block) {
|
|
177
|
+
const mappedStartLine = block.startLine + diagnostic.line - 1;
|
|
178
|
+
const mappedEndLine = diagnostic.endLine !== void 0 ? block.startLine + diagnostic.endLine - 1 : void 0;
|
|
179
|
+
return {
|
|
180
|
+
...diagnostic,
|
|
181
|
+
line: mappedStartLine,
|
|
182
|
+
column: diagnostic.line === 1 ? block.startColumn + diagnostic.column - 1 : diagnostic.column,
|
|
183
|
+
...mappedEndLine !== void 0 ? { endLine: mappedEndLine } : {},
|
|
184
|
+
...diagnostic.endColumn !== void 0 ? { endColumn: diagnostic.endLine === void 0 || diagnostic.endLine === 1 ? block.startColumn + diagnostic.endColumn - 1 : diagnostic.endColumn } : {}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const lintRule = {
|
|
188
|
+
meta: {
|
|
189
|
+
type: "problem",
|
|
190
|
+
docs: { description: "Run Stylelint and surface its diagnostics through ESLint" },
|
|
191
|
+
schema: [{
|
|
192
|
+
type: "object",
|
|
193
|
+
additionalProperties: false,
|
|
194
|
+
properties: { cwd: { type: "string" } }
|
|
195
|
+
}]
|
|
196
|
+
},
|
|
197
|
+
create(context) {
|
|
198
|
+
return { Program() {
|
|
199
|
+
if (!context.filename.endsWith(".vue")) return;
|
|
200
|
+
const cwd = context.options[0]?.cwd;
|
|
201
|
+
const styleBlocks = extractVueStyleBlocks(context.sourceCode.text, context.filename);
|
|
202
|
+
for (const styleBlock of styleBlocks) {
|
|
203
|
+
const diagnostics = runStylelintSync(styleBlock.content, styleBlock.virtualFilename, cwd);
|
|
204
|
+
for (const diagnostic of diagnostics) {
|
|
205
|
+
const mappedDiagnostic = mapDiagnosticToVueFile(diagnostic, styleBlock);
|
|
206
|
+
context.report({
|
|
207
|
+
loc: {
|
|
208
|
+
start: {
|
|
209
|
+
line: mappedDiagnostic.line,
|
|
210
|
+
column: getColumn(mappedDiagnostic.column)
|
|
211
|
+
},
|
|
212
|
+
...mappedDiagnostic.endLine !== void 0 || mappedDiagnostic.endColumn !== void 0 ? { end: {
|
|
213
|
+
line: mappedDiagnostic.endLine ?? mappedDiagnostic.line,
|
|
214
|
+
column: getColumn(mappedDiagnostic.endColumn ?? mappedDiagnostic.column)
|
|
215
|
+
} } : {}
|
|
216
|
+
},
|
|
217
|
+
message: styleBlock.label ? `[${styleBlock.label}] ${mappedDiagnostic.ruleId ? `${mappedDiagnostic.message} (${mappedDiagnostic.ruleId})` : mappedDiagnostic.message}` : mappedDiagnostic.ruleId ? `${mappedDiagnostic.message} (${mappedDiagnostic.ruleId})` : mappedDiagnostic.message
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} };
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/index.ts
|
|
226
|
+
const plugin = {
|
|
227
|
+
meta: {
|
|
228
|
+
name: "stylelint",
|
|
229
|
+
version: "0.0.1"
|
|
230
|
+
},
|
|
231
|
+
processors: {
|
|
232
|
+
css: cssProcessor,
|
|
233
|
+
scss: scssProcessor
|
|
234
|
+
},
|
|
235
|
+
rules: { stylelint: lintRule }
|
|
236
|
+
};
|
|
237
|
+
//#endregion
|
|
238
|
+
export { cssProcessor, plugin as default, lintRule, runStylelintSync, scssProcessor };
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
//#region src/worker.d.ts
|
|
2
|
+
declare function createProjectRequire(cwd: string): NodeJS.Require;
|
|
3
|
+
declare function resolveStylelintEntry(cwd: string): string;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { createProjectRequire as __createProjectRequire, resolveStylelintEntry as __resolveStylelintEntry };
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { runAsWorker } from "synckit";
|
|
5
|
+
//#region src/worker.ts
|
|
6
|
+
function createProjectRequire(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return createRequire(path.join(cwd, "package.json"));
|
|
9
|
+
} catch {
|
|
10
|
+
return createRequire(import.meta.url);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function resolveStylelintEntry(cwd) {
|
|
14
|
+
const projectRequire = createProjectRequire(cwd);
|
|
15
|
+
try {
|
|
16
|
+
return projectRequire.resolve("stylelint");
|
|
17
|
+
} catch {
|
|
18
|
+
return createRequire(import.meta.url).resolve("stylelint");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function runStylelint(code, filename, cwd) {
|
|
22
|
+
try {
|
|
23
|
+
const stylelintModule = await import(pathToFileURL(resolveStylelintEntry(cwd)).href);
|
|
24
|
+
return {
|
|
25
|
+
ok: true,
|
|
26
|
+
result: (await (stylelintModule.default ?? stylelintModule).lint({
|
|
27
|
+
allowEmptyInput: true,
|
|
28
|
+
code,
|
|
29
|
+
codeFilename: filename,
|
|
30
|
+
cwd
|
|
31
|
+
})).results[0] ?? { warnings: [] }
|
|
32
|
+
};
|
|
33
|
+
} catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
runAsWorker(runStylelint);
|
|
41
|
+
//#endregion
|
|
42
|
+
export { createProjectRequire as __createProjectRequire, resolveStylelintEntry as __resolveStylelintEntry };
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-better-stylelint",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "Bridge Stylelint diagnostics into ESLint for style files and Vue SFCs",
|
|
6
|
+
"author": "ice breaker <1324318532@qq.com>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/sonofmagic/dev-configs.git",
|
|
11
|
+
"directory": "packages/eslint-plugin-better-stylelint"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sonofmagic/dev-configs/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"dev-configs",
|
|
18
|
+
"eslint",
|
|
19
|
+
"stylelint",
|
|
20
|
+
"eslint-plugin"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"main": "./dist/index.js",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"README.md",
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"eslint": "^9.0.0",
|
|
39
|
+
"stylelint": "^17.4.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"stylelint": {
|
|
43
|
+
"optional": true
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"stylelint": "^17.4.0",
|
|
48
|
+
"synckit": "^0.11.12"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public",
|
|
52
|
+
"registry": "https://registry.npmjs.org"
|
|
53
|
+
},
|
|
54
|
+
"tsd": {
|
|
55
|
+
"directory": "test-d",
|
|
56
|
+
"compilerOptions": {
|
|
57
|
+
"skipLibCheck": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"dev": "tsdown --watch --sourcemap",
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"lint": "eslint src test test-d",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"test:types": "tsd",
|
|
66
|
+
"test:dev": "vitest",
|
|
67
|
+
"release": "pnpm publish",
|
|
68
|
+
"sync": "cnpm sync eslint-plugin-better-stylelint",
|
|
69
|
+
"typecheck": "tsc --noEmit"
|
|
70
|
+
}
|
|
71
|
+
}
|