@jesscss/lint 2.0.0-alpha.11

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Matthew Dean
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/lib/index.cjs ADDED
@@ -0,0 +1,331 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let node_fs_promises = require("node:fs/promises");
25
+ let node_path = require("node:path");
26
+ node_path = __toESM(node_path);
27
+ let glob = require("glob");
28
+ let _jesscss_diagnostics_core = require("@jesscss/diagnostics-core");
29
+ let styles_config = require("styles-config");
30
+ //#region src/rules.ts
31
+ const PARSE_SYNTAX_ERROR_CODE = "parse/syntax-error";
32
+ const STABLE_LINT_RULE_SET_VERSION = 1;
33
+ const RECOMMENDED_DIAGNOSTICS = {
34
+ [PARSE_SYNTAX_ERROR_CODE]: "error",
35
+ [_jesscss_diagnostics_core.LINT_CODES.emptyRules]: "warn",
36
+ [_jesscss_diagnostics_core.LINT_CODES.unknownProperties]: "warn",
37
+ [_jesscss_diagnostics_core.LINT_CODES.unknownAtRules]: "warn",
38
+ [_jesscss_diagnostics_core.LINT_CODES.duplicateProperties]: "warn",
39
+ [_jesscss_diagnostics_core.LINT_CODES.hexColorLength]: "error",
40
+ [_jesscss_diagnostics_core.LINT_CODES.zeroUnits]: "warn",
41
+ [_jesscss_diagnostics_core.LINT_CODES.unsupportedSassForm]: "warn"
42
+ };
43
+ const COMPARISON_DIAGNOSTICS = {
44
+ [_jesscss_diagnostics_core.LINT_CODES.emptyRules]: "warn",
45
+ [_jesscss_diagnostics_core.LINT_CODES.unknownProperties]: "warn",
46
+ [_jesscss_diagnostics_core.LINT_CODES.unknownAtRules]: "warn",
47
+ [_jesscss_diagnostics_core.LINT_CODES.duplicateProperties]: "warn",
48
+ [_jesscss_diagnostics_core.LINT_CODES.hexColorLength]: "error",
49
+ [_jesscss_diagnostics_core.LINT_CODES.zeroUnits]: "warn"
50
+ };
51
+ const STABLE_LINT_RULES = [
52
+ {
53
+ code: PARSE_SYNTAX_ERROR_CODE,
54
+ title: "Syntax error",
55
+ tier: "syntax",
56
+ defaultPolicy: "error",
57
+ comparison: "jess-only",
58
+ notes: "Parser diagnostic surfaced through lint policy; Stylelint reports parser errors through its own parser path."
59
+ },
60
+ {
61
+ code: _jesscss_diagnostics_core.LINT_CODES.emptyRules,
62
+ title: "Empty rules",
63
+ tier: "maintainability",
64
+ defaultPolicy: "warn",
65
+ comparison: "stylelint-equivalent",
66
+ stylelintRule: "block-no-empty",
67
+ notes: "Flags empty qualified rules after Jess dialect parsing."
68
+ },
69
+ {
70
+ code: _jesscss_diagnostics_core.LINT_CODES.unknownProperties,
71
+ title: "Unknown properties",
72
+ tier: "css-validity",
73
+ defaultPolicy: "warn",
74
+ comparison: "stylelint-near",
75
+ stylelintRule: "property-no-unknown",
76
+ notes: "Uses Jess language metadata and suppresses dialect variables, custom properties, vendor-prefixed properties, and interpolated names."
77
+ },
78
+ {
79
+ code: _jesscss_diagnostics_core.LINT_CODES.unknownAtRules,
80
+ title: "Unknown at-rules",
81
+ tier: "css-validity",
82
+ defaultPolicy: "warn",
83
+ comparison: "stylelint-near",
84
+ stylelintRule: "at-rule-no-unknown",
85
+ notes: "Uses Jess language metadata plus dialect at-rule allow-lists."
86
+ },
87
+ {
88
+ code: _jesscss_diagnostics_core.LINT_CODES.duplicateProperties,
89
+ title: "Duplicate properties",
90
+ tier: "maintainability",
91
+ defaultPolicy: "warn",
92
+ comparison: "stylelint-equivalent",
93
+ stylelintRule: "declaration-block-no-duplicate-properties",
94
+ notes: "Flags duplicate declaration names in the same parsed block."
95
+ },
96
+ {
97
+ code: _jesscss_diagnostics_core.LINT_CODES.hexColorLength,
98
+ title: "Invalid hex colors",
99
+ tier: "css-validity",
100
+ defaultPolicy: "error",
101
+ comparison: "stylelint-equivalent",
102
+ stylelintRule: "color-no-invalid-hex",
103
+ notes: "Flags hex color literals whose digit count is not valid CSS."
104
+ },
105
+ {
106
+ code: _jesscss_diagnostics_core.LINT_CODES.zeroUnits,
107
+ title: "Zero length units",
108
+ tier: "style-suggestion",
109
+ defaultPolicy: "warn",
110
+ comparison: "stylelint-equivalent",
111
+ stylelintRule: "length-zero-no-unit",
112
+ notes: "Flags zero values with length units; non-length units such as percentages and time are left alone."
113
+ },
114
+ {
115
+ code: _jesscss_diagnostics_core.LINT_CODES.unsupportedSassForm,
116
+ title: "Unsupported Sass forms",
117
+ tier: "dialect-support",
118
+ defaultPolicy: "warn",
119
+ comparison: "jess-only",
120
+ notes: "Jess dialect support diagnostic shared with the language service."
121
+ }
122
+ ];
123
+ const RECOMMENDED_LINT_CONFIG = {
124
+ reportSyntax: true,
125
+ diagnostics: RECOMMENDED_DIAGNOSTICS
126
+ };
127
+ const STYLELINT_COMPARISON_LINT_CONFIG = {
128
+ reportSyntax: false,
129
+ diagnostics: COMPARISON_DIAGNOSTICS
130
+ };
131
+ function recommendedLintDiagnostics() {
132
+ return { ...RECOMMENDED_DIAGNOSTICS };
133
+ }
134
+ function stylelintComparisonDiagnostics() {
135
+ return { ...COMPARISON_DIAGNOSTICS };
136
+ }
137
+ //#endregion
138
+ //#region src/index.ts
139
+ const DEFAULT_FILE_PATTERNS = ["**/*.{css,less,scss,jess}"];
140
+ var SourceLineIndex = class {
141
+ #source;
142
+ #lineStarts;
143
+ #lines;
144
+ constructor(source) {
145
+ this.#source = source;
146
+ this.#lineStarts = [0];
147
+ for (let i = 0; i < source.length; i++) if (source.charCodeAt(i) === 10) this.#lineStarts.push(i + 1);
148
+ this.#lines = source.split(/\r?\n/);
149
+ }
150
+ lineColAt(offset) {
151
+ const end = Math.min(Math.max(0, offset), this.#source.length);
152
+ let low = 0;
153
+ let high = this.#lineStarts.length - 1;
154
+ while (low <= high) {
155
+ const mid = Math.floor((low + high) / 2);
156
+ if ((this.#lineStarts[mid] ?? 0) <= end) low = mid + 1;
157
+ else high = mid - 1;
158
+ }
159
+ const lineIndex = Math.max(0, high);
160
+ return {
161
+ line: lineIndex + 1,
162
+ column: end - (this.#lineStarts[lineIndex] ?? 0) + 1
163
+ };
164
+ }
165
+ extractRelevantLines(line, contextLines = 1) {
166
+ if (this.#source.length === 0) return;
167
+ const target = Math.max(1, Math.min(line, this.#lines.length));
168
+ const start = Math.max(1, target - contextLines);
169
+ const end = Math.min(this.#lines.length, target + contextLines);
170
+ const result = {};
171
+ for (let i = start; i <= end; i++) result[i] = this.#lines[i - 1] ?? "";
172
+ return result;
173
+ }
174
+ };
175
+ function lintConfigFromStylesConfig(config) {
176
+ return config?.lint;
177
+ }
178
+ function mergeLintConfig(base, override) {
179
+ return {
180
+ ...base,
181
+ ...override,
182
+ diagnostics: {
183
+ ...base?.diagnostics,
184
+ ...override?.diagnostics
185
+ }
186
+ };
187
+ }
188
+ async function resolveLintConfig(options) {
189
+ const cwd = options.cwd ?? process.cwd();
190
+ return mergeLintConfig(mergeLintConfig(RECOMMENDED_LINT_CONFIG, lintConfigFromStylesConfig(options.stylesConfig ?? (options.configFile ? await (0, styles_config.loadConfigFromPath)(options.configFile) : await (0, styles_config.loadConfig)(cwd)))), options.lintConfig);
191
+ }
192
+ function languageFromPath(filePath, fallback) {
193
+ if (fallback) return fallback;
194
+ const ext = node_path.default.extname(filePath ?? "").toLowerCase();
195
+ if (ext === ".less") return "less";
196
+ if (ext === ".scss") return "scss";
197
+ if (ext === ".jess") return "jess";
198
+ return "css";
199
+ }
200
+ function applyPolicy(diagnostics, config, options) {
201
+ const out = [];
202
+ for (const diagnostic of diagnostics) {
203
+ if (options.syntaxOnly === true && diagnostic.phase !== "parse") continue;
204
+ if (config.reportSyntax === false && diagnostic.phase === "parse") continue;
205
+ const policy = config.diagnostics?.[diagnostic.code];
206
+ if (policy === void 0 || policy === "off") continue;
207
+ out.push({
208
+ ...diagnostic,
209
+ severity: policy === "error" ? "error" : policy === "warn" ? "warning" : diagnostic.defaultSeverity
210
+ });
211
+ }
212
+ return out;
213
+ }
214
+ function toErrorDiagnostic(diagnostic, lines) {
215
+ const start = lines.lineColAt(diagnostic.start);
216
+ const end = lines.lineColAt(diagnostic.end);
217
+ return {
218
+ code: diagnostic.code,
219
+ phase: diagnostic.phase,
220
+ message: diagnostic.message,
221
+ reason: diagnostic.reason,
222
+ fix: diagnostic.fix,
223
+ filePath: diagnostic.filePath,
224
+ line: start.line,
225
+ column: start.column,
226
+ endLine: end.line,
227
+ endColumn: end.column,
228
+ lines: lines.extractRelevantLines(start.line)
229
+ };
230
+ }
231
+ function toWarningDiagnostic(diagnostic, lines) {
232
+ const start = lines.lineColAt(diagnostic.start);
233
+ const end = lines.lineColAt(diagnostic.end);
234
+ return {
235
+ code: diagnostic.code,
236
+ phase: diagnostic.phase,
237
+ message: diagnostic.message,
238
+ reason: diagnostic.reason,
239
+ fix: diagnostic.fix,
240
+ filePath: diagnostic.filePath,
241
+ line: start.line,
242
+ column: start.column,
243
+ endLine: end.line,
244
+ endColumn: end.column,
245
+ lines: lines.extractRelevantLines(start.line)
246
+ };
247
+ }
248
+ function toLintResult(source, filePath, diagnostics, includeLegacyDiagnostics) {
249
+ if (!includeLegacyDiagnostics) return {
250
+ filePath,
251
+ diagnostics,
252
+ errors: [],
253
+ warnings: []
254
+ };
255
+ const lines = new SourceLineIndex(source);
256
+ const errors = [];
257
+ const warnings = [];
258
+ for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errors.push(toErrorDiagnostic(diagnostic, lines));
259
+ else warnings.push(toWarningDiagnostic(diagnostic, lines));
260
+ return {
261
+ filePath,
262
+ diagnostics,
263
+ errors,
264
+ warnings
265
+ };
266
+ }
267
+ async function lintText(input, options = {}) {
268
+ const lintConfig = await resolveLintConfig(options);
269
+ const language = languageFromPath(input.filePath, input.language ?? options.language);
270
+ const collected = (0, _jesscss_diagnostics_core.collectTolerantDiagnostics)({
271
+ source: input.source,
272
+ filePath: input.filePath,
273
+ language
274
+ });
275
+ return toLintResult(input.source, input.filePath, applyPolicy(collected.diagnostics, lintConfig, options), options.includeLegacyDiagnostics === true);
276
+ }
277
+ function patternsOf(value) {
278
+ if (value === void 0) return [];
279
+ return typeof value === "string" ? [value] : [...value];
280
+ }
281
+ async function lintFiles(patterns, options = {}) {
282
+ const cwd = options.cwd ?? process.cwd();
283
+ const lintConfig = await resolveLintConfig(options);
284
+ const inputPatterns = typeof patterns === "string" ? [patterns] : [...patterns];
285
+ const configuredPatterns = patternsOf(lintConfig.files);
286
+ const files = await (0, glob.glob)(inputPatterns.length > 0 ? inputPatterns : configuredPatterns.length > 0 ? configuredPatterns : DEFAULT_FILE_PATTERNS, {
287
+ cwd,
288
+ absolute: true,
289
+ nodir: true,
290
+ ignore: patternsOf(lintConfig.ignoreFiles)
291
+ });
292
+ files.sort();
293
+ const results = [];
294
+ for (const filePath of files) {
295
+ const source = await (0, node_fs_promises.readFile)(filePath, "utf8");
296
+ const collected = (0, _jesscss_diagnostics_core.collectTolerantDiagnostics)({
297
+ source,
298
+ filePath,
299
+ language: languageFromPath(filePath, options.language)
300
+ });
301
+ results.push(toLintResult(source, filePath, applyPolicy(collected.diagnostics, lintConfig, options), options.includeLegacyDiagnostics === true));
302
+ }
303
+ const errorCount = results.reduce((sum, result) => sum + result.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length, 0);
304
+ const warningCount = results.reduce((sum, result) => sum + result.diagnostics.filter((diagnostic) => diagnostic.severity !== "error").length, 0);
305
+ return {
306
+ results,
307
+ errorCount,
308
+ warningCount,
309
+ errored: errorCount > 0 || options.maxWarnings !== void 0 && warningCount > options.maxWarnings
310
+ };
311
+ }
312
+ function formatLintResult(result) {
313
+ const lines = [];
314
+ for (const file of result.results) for (const diagnostic of file.diagnostics) {
315
+ const loc = `${file.filePath ?? "<input>"}:${diagnostic.start}`;
316
+ lines.push(`${loc} ${diagnostic.severity} ${diagnostic.code} ${diagnostic.message}`);
317
+ }
318
+ lines.push(`Linted ${result.results.length} file(s): ${result.errorCount} error(s), ${result.warningCount} warning(s)`);
319
+ return lines.join("\n");
320
+ }
321
+ //#endregion
322
+ exports.PARSE_SYNTAX_ERROR_CODE = PARSE_SYNTAX_ERROR_CODE;
323
+ exports.RECOMMENDED_LINT_CONFIG = RECOMMENDED_LINT_CONFIG;
324
+ exports.STABLE_LINT_RULES = STABLE_LINT_RULES;
325
+ exports.STABLE_LINT_RULE_SET_VERSION = STABLE_LINT_RULE_SET_VERSION;
326
+ exports.STYLELINT_COMPARISON_LINT_CONFIG = STYLELINT_COMPARISON_LINT_CONFIG;
327
+ exports.formatLintResult = formatLintResult;
328
+ exports.lintFiles = lintFiles;
329
+ exports.lintText = lintText;
330
+ exports.recommendedLintDiagnostics = recommendedLintDiagnostics;
331
+ exports.stylelintComparisonDiagnostics = stylelintComparisonDiagnostics;
package/lib/index.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { type DiagnosticSeverityName, type JessLanguage, type SourceDiagnostic } from '@jesscss/diagnostics-core';
2
+ import { type ErrorDiagnostic, type WarningDiagnostic } from '@jesscss/core';
3
+ import type { LintConfig, LintSeverity, StylesConfig } from 'styles-config';
4
+ export type { LintConfig, LintSeverity };
5
+ export { PARSE_SYNTAX_ERROR_CODE, RECOMMENDED_LINT_CONFIG, STABLE_LINT_RULES, STABLE_LINT_RULE_SET_VERSION, STYLELINT_COMPARISON_LINT_CONFIG, recommendedLintDiagnostics, stylelintComparisonDiagnostics, type LintRuleComparisonKind, type LintRuleTier, type StableLintRule } from './rules.js';
6
+ export interface LintOptions {
7
+ readonly cwd?: string;
8
+ readonly configFile?: string;
9
+ readonly stylesConfig?: StylesConfig;
10
+ readonly lintConfig?: LintConfig;
11
+ readonly language?: JessLanguage;
12
+ readonly maxWarnings?: number;
13
+ readonly syntaxOnly?: boolean;
14
+ readonly includeLegacyDiagnostics?: boolean;
15
+ }
16
+ export interface LintTextInput {
17
+ readonly source: string;
18
+ readonly filePath?: string;
19
+ readonly language?: JessLanguage;
20
+ }
21
+ export interface LintDiagnostic extends SourceDiagnostic {
22
+ readonly severity: DiagnosticSeverityName;
23
+ }
24
+ export interface LintResult {
25
+ readonly filePath?: string;
26
+ readonly diagnostics: readonly LintDiagnostic[];
27
+ readonly errors: readonly ErrorDiagnostic[];
28
+ readonly warnings: readonly WarningDiagnostic[];
29
+ }
30
+ export interface LintRunResult {
31
+ readonly results: readonly LintResult[];
32
+ readonly errored: boolean;
33
+ readonly warningCount: number;
34
+ readonly errorCount: number;
35
+ }
36
+ export declare function lintText(input: LintTextInput, options?: LintOptions): Promise<LintResult>;
37
+ export declare function lintFiles(patterns: string | readonly string[], options?: LintOptions): Promise<LintRunResult>;
38
+ export declare function formatLintResult(result: LintRunResult): string;
package/lib/index.js ADDED
@@ -0,0 +1,298 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { glob } from "glob";
4
+ import { LINT_CODES, collectTolerantDiagnostics } from "@jesscss/diagnostics-core";
5
+ import { loadConfig, loadConfigFromPath } from "styles-config";
6
+ //#region src/rules.ts
7
+ const PARSE_SYNTAX_ERROR_CODE = "parse/syntax-error";
8
+ const STABLE_LINT_RULE_SET_VERSION = 1;
9
+ const RECOMMENDED_DIAGNOSTICS = {
10
+ [PARSE_SYNTAX_ERROR_CODE]: "error",
11
+ [LINT_CODES.emptyRules]: "warn",
12
+ [LINT_CODES.unknownProperties]: "warn",
13
+ [LINT_CODES.unknownAtRules]: "warn",
14
+ [LINT_CODES.duplicateProperties]: "warn",
15
+ [LINT_CODES.hexColorLength]: "error",
16
+ [LINT_CODES.zeroUnits]: "warn",
17
+ [LINT_CODES.unsupportedSassForm]: "warn"
18
+ };
19
+ const COMPARISON_DIAGNOSTICS = {
20
+ [LINT_CODES.emptyRules]: "warn",
21
+ [LINT_CODES.unknownProperties]: "warn",
22
+ [LINT_CODES.unknownAtRules]: "warn",
23
+ [LINT_CODES.duplicateProperties]: "warn",
24
+ [LINT_CODES.hexColorLength]: "error",
25
+ [LINT_CODES.zeroUnits]: "warn"
26
+ };
27
+ const STABLE_LINT_RULES = [
28
+ {
29
+ code: PARSE_SYNTAX_ERROR_CODE,
30
+ title: "Syntax error",
31
+ tier: "syntax",
32
+ defaultPolicy: "error",
33
+ comparison: "jess-only",
34
+ notes: "Parser diagnostic surfaced through lint policy; Stylelint reports parser errors through its own parser path."
35
+ },
36
+ {
37
+ code: LINT_CODES.emptyRules,
38
+ title: "Empty rules",
39
+ tier: "maintainability",
40
+ defaultPolicy: "warn",
41
+ comparison: "stylelint-equivalent",
42
+ stylelintRule: "block-no-empty",
43
+ notes: "Flags empty qualified rules after Jess dialect parsing."
44
+ },
45
+ {
46
+ code: LINT_CODES.unknownProperties,
47
+ title: "Unknown properties",
48
+ tier: "css-validity",
49
+ defaultPolicy: "warn",
50
+ comparison: "stylelint-near",
51
+ stylelintRule: "property-no-unknown",
52
+ notes: "Uses Jess language metadata and suppresses dialect variables, custom properties, vendor-prefixed properties, and interpolated names."
53
+ },
54
+ {
55
+ code: LINT_CODES.unknownAtRules,
56
+ title: "Unknown at-rules",
57
+ tier: "css-validity",
58
+ defaultPolicy: "warn",
59
+ comparison: "stylelint-near",
60
+ stylelintRule: "at-rule-no-unknown",
61
+ notes: "Uses Jess language metadata plus dialect at-rule allow-lists."
62
+ },
63
+ {
64
+ code: LINT_CODES.duplicateProperties,
65
+ title: "Duplicate properties",
66
+ tier: "maintainability",
67
+ defaultPolicy: "warn",
68
+ comparison: "stylelint-equivalent",
69
+ stylelintRule: "declaration-block-no-duplicate-properties",
70
+ notes: "Flags duplicate declaration names in the same parsed block."
71
+ },
72
+ {
73
+ code: LINT_CODES.hexColorLength,
74
+ title: "Invalid hex colors",
75
+ tier: "css-validity",
76
+ defaultPolicy: "error",
77
+ comparison: "stylelint-equivalent",
78
+ stylelintRule: "color-no-invalid-hex",
79
+ notes: "Flags hex color literals whose digit count is not valid CSS."
80
+ },
81
+ {
82
+ code: LINT_CODES.zeroUnits,
83
+ title: "Zero length units",
84
+ tier: "style-suggestion",
85
+ defaultPolicy: "warn",
86
+ comparison: "stylelint-equivalent",
87
+ stylelintRule: "length-zero-no-unit",
88
+ notes: "Flags zero values with length units; non-length units such as percentages and time are left alone."
89
+ },
90
+ {
91
+ code: LINT_CODES.unsupportedSassForm,
92
+ title: "Unsupported Sass forms",
93
+ tier: "dialect-support",
94
+ defaultPolicy: "warn",
95
+ comparison: "jess-only",
96
+ notes: "Jess dialect support diagnostic shared with the language service."
97
+ }
98
+ ];
99
+ const RECOMMENDED_LINT_CONFIG = {
100
+ reportSyntax: true,
101
+ diagnostics: RECOMMENDED_DIAGNOSTICS
102
+ };
103
+ const STYLELINT_COMPARISON_LINT_CONFIG = {
104
+ reportSyntax: false,
105
+ diagnostics: COMPARISON_DIAGNOSTICS
106
+ };
107
+ function recommendedLintDiagnostics() {
108
+ return { ...RECOMMENDED_DIAGNOSTICS };
109
+ }
110
+ function stylelintComparisonDiagnostics() {
111
+ return { ...COMPARISON_DIAGNOSTICS };
112
+ }
113
+ //#endregion
114
+ //#region src/index.ts
115
+ const DEFAULT_FILE_PATTERNS = ["**/*.{css,less,scss,jess}"];
116
+ var SourceLineIndex = class {
117
+ #source;
118
+ #lineStarts;
119
+ #lines;
120
+ constructor(source) {
121
+ this.#source = source;
122
+ this.#lineStarts = [0];
123
+ for (let i = 0; i < source.length; i++) if (source.charCodeAt(i) === 10) this.#lineStarts.push(i + 1);
124
+ this.#lines = source.split(/\r?\n/);
125
+ }
126
+ lineColAt(offset) {
127
+ const end = Math.min(Math.max(0, offset), this.#source.length);
128
+ let low = 0;
129
+ let high = this.#lineStarts.length - 1;
130
+ while (low <= high) {
131
+ const mid = Math.floor((low + high) / 2);
132
+ if ((this.#lineStarts[mid] ?? 0) <= end) low = mid + 1;
133
+ else high = mid - 1;
134
+ }
135
+ const lineIndex = Math.max(0, high);
136
+ return {
137
+ line: lineIndex + 1,
138
+ column: end - (this.#lineStarts[lineIndex] ?? 0) + 1
139
+ };
140
+ }
141
+ extractRelevantLines(line, contextLines = 1) {
142
+ if (this.#source.length === 0) return;
143
+ const target = Math.max(1, Math.min(line, this.#lines.length));
144
+ const start = Math.max(1, target - contextLines);
145
+ const end = Math.min(this.#lines.length, target + contextLines);
146
+ const result = {};
147
+ for (let i = start; i <= end; i++) result[i] = this.#lines[i - 1] ?? "";
148
+ return result;
149
+ }
150
+ };
151
+ function lintConfigFromStylesConfig(config) {
152
+ return config?.lint;
153
+ }
154
+ function mergeLintConfig(base, override) {
155
+ return {
156
+ ...base,
157
+ ...override,
158
+ diagnostics: {
159
+ ...base?.diagnostics,
160
+ ...override?.diagnostics
161
+ }
162
+ };
163
+ }
164
+ async function resolveLintConfig(options) {
165
+ const cwd = options.cwd ?? process.cwd();
166
+ return mergeLintConfig(mergeLintConfig(RECOMMENDED_LINT_CONFIG, lintConfigFromStylesConfig(options.stylesConfig ?? (options.configFile ? await loadConfigFromPath(options.configFile) : await loadConfig(cwd)))), options.lintConfig);
167
+ }
168
+ function languageFromPath(filePath, fallback) {
169
+ if (fallback) return fallback;
170
+ const ext = path.extname(filePath ?? "").toLowerCase();
171
+ if (ext === ".less") return "less";
172
+ if (ext === ".scss") return "scss";
173
+ if (ext === ".jess") return "jess";
174
+ return "css";
175
+ }
176
+ function applyPolicy(diagnostics, config, options) {
177
+ const out = [];
178
+ for (const diagnostic of diagnostics) {
179
+ if (options.syntaxOnly === true && diagnostic.phase !== "parse") continue;
180
+ if (config.reportSyntax === false && diagnostic.phase === "parse") continue;
181
+ const policy = config.diagnostics?.[diagnostic.code];
182
+ if (policy === void 0 || policy === "off") continue;
183
+ out.push({
184
+ ...diagnostic,
185
+ severity: policy === "error" ? "error" : policy === "warn" ? "warning" : diagnostic.defaultSeverity
186
+ });
187
+ }
188
+ return out;
189
+ }
190
+ function toErrorDiagnostic(diagnostic, lines) {
191
+ const start = lines.lineColAt(diagnostic.start);
192
+ const end = lines.lineColAt(diagnostic.end);
193
+ return {
194
+ code: diagnostic.code,
195
+ phase: diagnostic.phase,
196
+ message: diagnostic.message,
197
+ reason: diagnostic.reason,
198
+ fix: diagnostic.fix,
199
+ filePath: diagnostic.filePath,
200
+ line: start.line,
201
+ column: start.column,
202
+ endLine: end.line,
203
+ endColumn: end.column,
204
+ lines: lines.extractRelevantLines(start.line)
205
+ };
206
+ }
207
+ function toWarningDiagnostic(diagnostic, lines) {
208
+ const start = lines.lineColAt(diagnostic.start);
209
+ const end = lines.lineColAt(diagnostic.end);
210
+ return {
211
+ code: diagnostic.code,
212
+ phase: diagnostic.phase,
213
+ message: diagnostic.message,
214
+ reason: diagnostic.reason,
215
+ fix: diagnostic.fix,
216
+ filePath: diagnostic.filePath,
217
+ line: start.line,
218
+ column: start.column,
219
+ endLine: end.line,
220
+ endColumn: end.column,
221
+ lines: lines.extractRelevantLines(start.line)
222
+ };
223
+ }
224
+ function toLintResult(source, filePath, diagnostics, includeLegacyDiagnostics) {
225
+ if (!includeLegacyDiagnostics) return {
226
+ filePath,
227
+ diagnostics,
228
+ errors: [],
229
+ warnings: []
230
+ };
231
+ const lines = new SourceLineIndex(source);
232
+ const errors = [];
233
+ const warnings = [];
234
+ for (const diagnostic of diagnostics) if (diagnostic.severity === "error") errors.push(toErrorDiagnostic(diagnostic, lines));
235
+ else warnings.push(toWarningDiagnostic(diagnostic, lines));
236
+ return {
237
+ filePath,
238
+ diagnostics,
239
+ errors,
240
+ warnings
241
+ };
242
+ }
243
+ async function lintText(input, options = {}) {
244
+ const lintConfig = await resolveLintConfig(options);
245
+ const language = languageFromPath(input.filePath, input.language ?? options.language);
246
+ const collected = collectTolerantDiagnostics({
247
+ source: input.source,
248
+ filePath: input.filePath,
249
+ language
250
+ });
251
+ return toLintResult(input.source, input.filePath, applyPolicy(collected.diagnostics, lintConfig, options), options.includeLegacyDiagnostics === true);
252
+ }
253
+ function patternsOf(value) {
254
+ if (value === void 0) return [];
255
+ return typeof value === "string" ? [value] : [...value];
256
+ }
257
+ async function lintFiles(patterns, options = {}) {
258
+ const cwd = options.cwd ?? process.cwd();
259
+ const lintConfig = await resolveLintConfig(options);
260
+ const inputPatterns = typeof patterns === "string" ? [patterns] : [...patterns];
261
+ const configuredPatterns = patternsOf(lintConfig.files);
262
+ const files = await glob(inputPatterns.length > 0 ? inputPatterns : configuredPatterns.length > 0 ? configuredPatterns : DEFAULT_FILE_PATTERNS, {
263
+ cwd,
264
+ absolute: true,
265
+ nodir: true,
266
+ ignore: patternsOf(lintConfig.ignoreFiles)
267
+ });
268
+ files.sort();
269
+ const results = [];
270
+ for (const filePath of files) {
271
+ const source = await readFile(filePath, "utf8");
272
+ const collected = collectTolerantDiagnostics({
273
+ source,
274
+ filePath,
275
+ language: languageFromPath(filePath, options.language)
276
+ });
277
+ results.push(toLintResult(source, filePath, applyPolicy(collected.diagnostics, lintConfig, options), options.includeLegacyDiagnostics === true));
278
+ }
279
+ const errorCount = results.reduce((sum, result) => sum + result.diagnostics.filter((diagnostic) => diagnostic.severity === "error").length, 0);
280
+ const warningCount = results.reduce((sum, result) => sum + result.diagnostics.filter((diagnostic) => diagnostic.severity !== "error").length, 0);
281
+ return {
282
+ results,
283
+ errorCount,
284
+ warningCount,
285
+ errored: errorCount > 0 || options.maxWarnings !== void 0 && warningCount > options.maxWarnings
286
+ };
287
+ }
288
+ function formatLintResult(result) {
289
+ const lines = [];
290
+ for (const file of result.results) for (const diagnostic of file.diagnostics) {
291
+ const loc = `${file.filePath ?? "<input>"}:${diagnostic.start}`;
292
+ lines.push(`${loc} ${diagnostic.severity} ${diagnostic.code} ${diagnostic.message}`);
293
+ }
294
+ lines.push(`Linted ${result.results.length} file(s): ${result.errorCount} error(s), ${result.warningCount} warning(s)`);
295
+ return lines.join("\n");
296
+ }
297
+ //#endregion
298
+ export { PARSE_SYNTAX_ERROR_CODE, RECOMMENDED_LINT_CONFIG, STABLE_LINT_RULES, STABLE_LINT_RULE_SET_VERSION, STYLELINT_COMPARISON_LINT_CONFIG, formatLintResult, lintFiles, lintText, recommendedLintDiagnostics, stylelintComparisonDiagnostics };
package/lib/rules.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { LintConfig, LintSeverity } from 'styles-config';
2
+ export declare const PARSE_SYNTAX_ERROR_CODE = "parse/syntax-error";
3
+ export declare const STABLE_LINT_RULE_SET_VERSION = 1;
4
+ export type LintRuleComparisonKind = 'stylelint-equivalent' | 'stylelint-near' | 'jess-only';
5
+ export type LintRuleTier = 'syntax' | 'css-validity' | 'maintainability' | 'style-suggestion' | 'dialect-support';
6
+ export interface StableLintRule {
7
+ readonly code: string;
8
+ readonly title: string;
9
+ readonly tier: LintRuleTier;
10
+ readonly defaultPolicy: LintSeverity;
11
+ readonly comparison: LintRuleComparisonKind;
12
+ readonly stylelintRule?: string;
13
+ readonly notes: string;
14
+ }
15
+ export declare const STABLE_LINT_RULES: readonly StableLintRule[];
16
+ export declare const RECOMMENDED_LINT_CONFIG: LintConfig;
17
+ export declare const STYLELINT_COMPARISON_LINT_CONFIG: LintConfig;
18
+ export declare function recommendedLintDiagnostics(): Record<string, LintSeverity>;
19
+ export declare function stylelintComparisonDiagnostics(): Record<string, LintSeverity>;
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@jesscss/lint",
3
+ "type": "module",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Jess stylesheet lint policy and reporting",
8
+ "version": "2.0.0-alpha.11",
9
+ "engines": {
10
+ "node": "^20.19.0 || >=22.12.0"
11
+ },
12
+ "main": "lib/index.cjs",
13
+ "types": "lib/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./lib/index.d.ts",
17
+ "import": "./lib/index.js",
18
+ "require": "./lib/index.cjs"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "files": [
23
+ "lib"
24
+ ],
25
+ "dependencies": {
26
+ "glob": "^10.2.1",
27
+ "@jesscss/core": "2.0.0-alpha.11",
28
+ "styles-config": "2.0.0-alpha.11",
29
+ "@jesscss/diagnostics-core": "2.0.0-alpha.11"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "postcss-less": "~6.0.0",
34
+ "stylelint": "~17.14.1",
35
+ "typescript": "~5.8.2",
36
+ "vitest": "^4.1.0"
37
+ },
38
+ "author": "Matthew Dean <matthew-dean@users.noreply.github.com>",
39
+ "license": "MIT",
40
+ "bugs": {
41
+ "url": "https://github.com/jesscss/jess/issues"
42
+ },
43
+ "homepage": "https://github.com/jesscss/jess#readme",
44
+ "module": "lib/index.js",
45
+ "scripts": {
46
+ "build": "pnpm compile",
47
+ "compile": "tsdown --tsconfig tsconfig.build.json --no-dts && tsc -p tsconfig.build.json --emitDeclarationOnly --noCheck",
48
+ "bench:stylelint": "pnpm build && node test/stylelint-comparison-bench.mjs",
49
+ "test": "vitest --watch=false",
50
+ "lint": "eslint '**/*.{js,ts}'"
51
+ }
52
+ }