zemdomu 1.1.5 → 1.3.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/README.md +128 -12
- package/out/linter.js +0 -164
- package/out/src/cli.js +88 -0
- package/out/{component-analyzer.js → src/component-analyzer.js} +620 -461
- package/out/src/html-visitor.js +13 -0
- package/out/src/index.js +25 -0
- package/out/src/linter.js +272 -0
- package/out/src/performance-diagnostics.js +47 -0
- package/out/{project-linter.js → src/project-linter.js} +114 -87
- package/out/{rules → src/rules}/enforceListNesting.js +81 -81
- package/out/{rules → src/rules}/noTabindexGreaterThanZero.js +33 -33
- package/out/{rules → src/rules}/preventEmptyInlineTags.js +85 -85
- package/out/{rules → src/rules}/requireLinkText.js +101 -86
- package/out/src/rules/requireNavLinks.js +88 -0
- package/out/src/sarif.js +54 -0
- package/out/{simpleHtmlParser.js → src/simpleHtmlParser.js} +4 -0
- package/out/src/utils/collectLocalDeps.js +132 -0
- package/out/tests/cli-custom-rule.test.js +42 -0
- package/out/tests/cross-duplicate-ids-tsx.test.js +36 -0
- package/out/tests/crossComponent/Button.js +7 -0
- package/out/tests/crossComponent/Page.js +11 -0
- package/out/tests/crossComponent/Section.js +11 -0
- package/out/tests/crossComponent/SubSection.js +11 -0
- package/out/tests/crossComponent/cross-heading-order-alias.test.js +31 -0
- package/out/tests/crossComponent/cross-heading-order-entry-only.test.js +31 -0
- package/out/tests/crossComponent/cross-heading-order.test.js +45 -0
- package/out/tests/custom-rule-tsx.test.js +24 -0
- package/out/tests/edge-cases.test.js +17 -0
- package/out/tests/html-visitor.test.js +29 -0
- package/out/tests/linter.test.js +41 -0
- package/out/tests/parse-error.test.js +11 -0
- package/out/tests/performance-diagnostics.test.js +12 -0
- package/out/tests/sarif-output.test.js +15 -0
- package/out/tests/tsx-parse-error.test.js +11 -0
- package/out/tests/unique-ids-html.test.js +19 -0
- package/out/tests/unique-ids-tsx.test.js +19 -0
- package/package.json +33 -9
- package/out/index.js +0 -12
- package/out/rules/requireNavLinks.js +0 -48
- /package/out/{component-path-resolver.js → src/component-path-resolver.js} +0 -0
- /package/out/{rules → src/rules}/enforceHeadingOrder.js +0 -0
- /package/out/{rules → src/rules}/requireAltText.js +0 -0
- /package/out/{rules → src/rules}/requireAltTextJSX.js +0 -0
- /package/out/{rules → src/rules}/requireButtonText.js +0 -0
- /package/out/{rules → src/rules}/requireHrefOnAnchors.js +0 -0
- /package/out/{rules → src/rules}/requireHtmlLang.js +0 -0
- /package/out/{rules → src/rules}/requireIframeTitle.js +0 -0
- /package/out/{rules → src/rules}/requireImageInputAlt.js +0 -0
- /package/out/{rules → src/rules}/requireLabelForFormControls.js +0 -0
- /package/out/{rules → src/rules}/requireSectionHeading.js +0 -0
- /package/out/{rules → src/rules}/requireTableCaption.js +0 -0
- /package/out/{rules → src/rules}/singleH1.js +0 -0
- /package/out/{rules → src/rules}/uniqueIds.js +0 -0
- /package/out/{rules → src/rules}/utils.js +0 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# ZemDomu
|
|
1
|
+
# ZemDomu
|
|
2
2
|
|
|
3
3
|
Semantic HTML linting engine for clean, accessible and SEO-friendly markup. This package provides the shared core logic used by the ZemDomu VS Code extension and upcoming GitHub Action.
|
|
4
4
|
|
|
5
|
-
## 🧠 What is ZemDomu
|
|
5
|
+
## 🧠 What is ZemDomu?
|
|
6
6
|
|
|
7
7
|
**ZemDomu** is a semantic-first linter that helps developers write better HTML and JSX by catching accessibility and structural issues. It parses `.html`, `.jsx` and `.tsx` files and exposes a simple `lint()` function that returns semantic violations.
|
|
8
8
|
|
|
@@ -18,16 +18,20 @@ yarn add zemdomu
|
|
|
18
18
|
|
|
19
19
|
- ✅ Lint semantic issues in HTML, JSX and TSX
|
|
20
20
|
- 📦 Works in Node.js, CI or any JS runtime
|
|
21
|
-
- ⚙️ Extensible rule system
|
|
21
|
+
- ⚙️ Extensible rule system with simple custom rules
|
|
22
|
+
- 🔀 Cross-component analysis for React/JSX projects
|
|
23
|
+
- 🚀 Command line interface with `--custom` and `--cross`
|
|
24
|
+
- ⚠️ Configurable rule severity (`error`, `warning`, `off`)
|
|
25
|
+
- 📈 Performance diagnostics for profiling lint runs
|
|
22
26
|
- 📚 Shared by the extension and GitHub Action
|
|
23
27
|
- 🧪 Simple API: `lint(content, options)`
|
|
24
28
|
|
|
25
29
|
## ⚙️ Usage Example
|
|
26
30
|
|
|
27
31
|
```ts
|
|
28
|
-
import { lint } from
|
|
32
|
+
import { lint } from "zemdomu";
|
|
29
33
|
|
|
30
|
-
const html =
|
|
34
|
+
const html = "<img>";
|
|
31
35
|
const results = lint(html, { rules: { requireAltText: true } });
|
|
32
36
|
|
|
33
37
|
console.log(results);
|
|
@@ -41,7 +45,7 @@ console.log(results);
|
|
|
41
45
|
// ]
|
|
42
46
|
|
|
43
47
|
// Custom rules can be supplied via the `customRules` option
|
|
44
|
-
// const myRule = { name: 'demo',
|
|
48
|
+
// const myRule = { name: 'demo', test: node => false, message: 'demo' };
|
|
45
49
|
// lint(html, { customRules: [myRule] });
|
|
46
50
|
```
|
|
47
51
|
|
|
@@ -52,21 +56,30 @@ console.log(results);
|
|
|
52
56
|
**Parameters**
|
|
53
57
|
|
|
54
58
|
- `content` — HTML, JSX or TSX string input
|
|
55
|
-
- `options.rules` —
|
|
59
|
+
- `options.rules` — severity settings for built-in rules
|
|
56
60
|
- `options.customRules` — array of additional rules
|
|
61
|
+
- `options.filePath` — optional source file path
|
|
62
|
+
- `options.perf` — attach a `PerformanceRecorder` instance
|
|
57
63
|
|
|
58
64
|
**Example `LinterOptions`**
|
|
59
65
|
|
|
60
66
|
```ts
|
|
61
67
|
interface LinterOptions {
|
|
62
|
-
rules
|
|
63
|
-
requireAltText: boolean;
|
|
64
|
-
// ...more rules to come
|
|
65
|
-
};
|
|
68
|
+
rules?: Record<string, 'error' | 'warning' | 'off'>;
|
|
66
69
|
customRules?: Rule[];
|
|
70
|
+
filePath?: string;
|
|
71
|
+
perf?: PerformanceRecorder;
|
|
67
72
|
}
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
Example enabling one rule as a warning:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const results = lint(html, {
|
|
79
|
+
rules: { requireAltText: 'warning', uniqueIds: 'error' }
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
70
83
|
**Example `LintResult`**
|
|
71
84
|
|
|
72
85
|
```ts
|
|
@@ -78,9 +91,112 @@ interface LintResult {
|
|
|
78
91
|
}
|
|
79
92
|
```
|
|
80
93
|
|
|
94
|
+
## 🛠 CLI Usage
|
|
95
|
+
|
|
96
|
+
Run the linter from the command line by installing the package globally or using
|
|
97
|
+
`npx`. Provide one or more glob patterns to specify the files to lint. Patterns
|
|
98
|
+
may be separated by spaces, commas or newlines:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx zemdomu "src/**/*.{html,jsx,tsx}" --custom my-rule.js
|
|
102
|
+
npx zemdomu "src/**/*.html,src/**/*.jsx"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use `--custom` (or `-c`) to provide a path to a JavaScript or TypeScript module
|
|
106
|
+
exporting a custom rule or array of rules. Use `--cross` to enable cross
|
|
107
|
+
component analysis.
|
|
108
|
+
|
|
109
|
+
### Cross-Component Analysis
|
|
110
|
+
|
|
111
|
+
When analysing JSX projects you can track `<h1>` usage or similar patterns
|
|
112
|
+
across component boundaries. Instantiate `ProjectLinter` with the
|
|
113
|
+
`crossComponentAnalysis` option or pass `--cross` to the CLI. Use
|
|
114
|
+
`crossComponentDepth` (or `--cross-depth`) to limit how deep component trees are
|
|
115
|
+
traversed during analysis:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { ProjectLinter } from 'zemdomu';
|
|
119
|
+
const linter = new ProjectLinter({ crossComponentAnalysis: true, crossComponentDepth: 2 });
|
|
120
|
+
await linter.lintFile('App.jsx');
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
npx zemdomu "src/**/*.{jsx,tsx}" --cross --cross-depth 2
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Performance Diagnostics
|
|
128
|
+
|
|
129
|
+
Attach a `PerformanceDiagnostics` recorder to gather timing information for each
|
|
130
|
+
file and rule:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { lint, PerformanceDiagnostics } from 'zemdomu';
|
|
134
|
+
const perf = new PerformanceDiagnostics();
|
|
135
|
+
lint(code, { perf });
|
|
136
|
+
console.log(perf.getAsJSON());
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 📝 Writing Custom Rules
|
|
140
|
+
|
|
141
|
+
Custom rules are simple objects implementing the `Rule` interface. At minimum
|
|
142
|
+
provide a `name`, a `test` function that returns `true` when a node violates the
|
|
143
|
+
rule and a `message` describing the problem:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
interface Rule {
|
|
147
|
+
name: string;
|
|
148
|
+
test(node: any): boolean;
|
|
149
|
+
message: string;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// my-rule.js
|
|
155
|
+
module.exports = {
|
|
156
|
+
name: 'noFooDiv',
|
|
157
|
+
test: node => node.type === 'element' && node.tagName === 'foo',
|
|
158
|
+
message: '<foo> is not allowed'
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Use it programmatically:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { lint } from 'zemdomu';
|
|
166
|
+
const results = lint('<foo></foo>', { customRules: [require('./my-rule')] });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Helper Utilities
|
|
170
|
+
|
|
171
|
+
For more advanced rules you may need direct access to the parsed HTML or JSX
|
|
172
|
+
AST. ZemDomu exposes a few helpers to make this easier:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import {
|
|
176
|
+
parseHtml,
|
|
177
|
+
visitHtml,
|
|
178
|
+
getAttr,
|
|
179
|
+
getJsxAttr,
|
|
180
|
+
getTag,
|
|
181
|
+
ElementNode,
|
|
182
|
+
HtmlVisitor,
|
|
183
|
+
} from 'zemdomu';
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`parseHtml` returns the root `ElementNode`. The `visitHtml` function performs a
|
|
187
|
+
simple depth‑first traversal using an `HtmlVisitor` with optional `enter` and
|
|
188
|
+
`exit` callbacks. Utility functions like `getAttr` and `getJsxAttr` help reading
|
|
189
|
+
attributes, while `getTag` resolves JSX element names.
|
|
190
|
+
|
|
191
|
+
Or via the CLI:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx zemdomu file.html --custom my-rule.js
|
|
195
|
+
```
|
|
196
|
+
|
|
81
197
|
## 🔗 Related Tools
|
|
82
198
|
|
|
83
|
-
- [ZemDomu VS Code Extension](
|
|
199
|
+
- [ZemDomu VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ZachariasErydBerlin.zemdomu)
|
|
84
200
|
- ZemDomu GitHub Action (coming soon)
|
|
85
201
|
|
|
86
202
|
## 🛠 Development
|
package/out/linter.js
CHANGED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.lint = lint;
|
|
7
|
-
const simpleHtmlParser_1 = require("./simpleHtmlParser");
|
|
8
|
-
const parser_1 = require("@babel/parser");
|
|
9
|
-
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
10
|
-
const requireAltText_1 = __importDefault(require("./rules/requireAltText"));
|
|
11
|
-
const requireSectionHeading_1 = __importDefault(require("./rules/requireSectionHeading"));
|
|
12
|
-
const enforceHeadingOrder_1 = __importDefault(require("./rules/enforceHeadingOrder"));
|
|
13
|
-
const singleH1_1 = __importDefault(require("./rules/singleH1"));
|
|
14
|
-
const requireLabelForFormControls_1 = __importDefault(require("./rules/requireLabelForFormControls"));
|
|
15
|
-
const enforceListNesting_1 = __importDefault(require("./rules/enforceListNesting"));
|
|
16
|
-
const requireLinkText_1 = __importDefault(require("./rules/requireLinkText"));
|
|
17
|
-
const requireTableCaption_1 = __importDefault(require("./rules/requireTableCaption"));
|
|
18
|
-
const preventEmptyInlineTags_1 = __importDefault(require("./rules/preventEmptyInlineTags"));
|
|
19
|
-
const requireHrefOnAnchors_1 = __importDefault(require("./rules/requireHrefOnAnchors"));
|
|
20
|
-
const requireButtonText_1 = __importDefault(require("./rules/requireButtonText"));
|
|
21
|
-
const requireIframeTitle_1 = __importDefault(require("./rules/requireIframeTitle"));
|
|
22
|
-
const requireHtmlLang_1 = __importDefault(require("./rules/requireHtmlLang"));
|
|
23
|
-
const requireImageInputAlt_1 = __importDefault(require("./rules/requireImageInputAlt"));
|
|
24
|
-
const requireNavLinks_1 = __importDefault(require("./rules/requireNavLinks"));
|
|
25
|
-
const uniqueIds_1 = __importDefault(require("./rules/uniqueIds"));
|
|
26
|
-
const noTabindexGreaterThanZero_1 = __importDefault(require("./rules/noTabindexGreaterThanZero"));
|
|
27
|
-
const builtInRules = {
|
|
28
|
-
requireSectionHeading: requireSectionHeading_1.default,
|
|
29
|
-
enforceHeadingOrder: enforceHeadingOrder_1.default,
|
|
30
|
-
singleH1: singleH1_1.default,
|
|
31
|
-
requireAltText: requireAltText_1.default,
|
|
32
|
-
requireLabelForFormControls: requireLabelForFormControls_1.default,
|
|
33
|
-
enforceListNesting: enforceListNesting_1.default,
|
|
34
|
-
requireLinkText: requireLinkText_1.default,
|
|
35
|
-
requireTableCaption: requireTableCaption_1.default,
|
|
36
|
-
preventEmptyInlineTags: preventEmptyInlineTags_1.default,
|
|
37
|
-
requireHrefOnAnchors: requireHrefOnAnchors_1.default,
|
|
38
|
-
requireButtonText: requireButtonText_1.default,
|
|
39
|
-
requireIframeTitle: requireIframeTitle_1.default,
|
|
40
|
-
requireHtmlLang: requireHtmlLang_1.default,
|
|
41
|
-
requireImageInputAlt: requireImageInputAlt_1.default,
|
|
42
|
-
requireNavLinks: requireNavLinks_1.default,
|
|
43
|
-
uniqueIds: uniqueIds_1.default,
|
|
44
|
-
noTabindexGreaterThanZero: noTabindexGreaterThanZero_1.default,
|
|
45
|
-
};
|
|
46
|
-
const defaultOptions = {
|
|
47
|
-
rules: {
|
|
48
|
-
requireSectionHeading: true,
|
|
49
|
-
enforceHeadingOrder: true,
|
|
50
|
-
singleH1: true,
|
|
51
|
-
requireAltText: true,
|
|
52
|
-
requireLabelForFormControls: true,
|
|
53
|
-
enforceListNesting: true,
|
|
54
|
-
requireLinkText: true,
|
|
55
|
-
requireTableCaption: true,
|
|
56
|
-
preventEmptyInlineTags: true,
|
|
57
|
-
requireHrefOnAnchors: true,
|
|
58
|
-
requireButtonText: true,
|
|
59
|
-
requireIframeTitle: true,
|
|
60
|
-
requireHtmlLang: true,
|
|
61
|
-
requireImageInputAlt: true,
|
|
62
|
-
requireNavLinks: true,
|
|
63
|
-
uniqueIds: true,
|
|
64
|
-
noTabindexGreaterThanZero: true,
|
|
65
|
-
},
|
|
66
|
-
customRules: [],
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Lint HTML/JSX/TSX content.
|
|
70
|
-
*/
|
|
71
|
-
function lint(content, options = defaultOptions) {
|
|
72
|
-
var _a;
|
|
73
|
-
const opts = {
|
|
74
|
-
rules: { ...defaultOptions.rules, ...(options.rules || {}) },
|
|
75
|
-
customRules: (_a = options.customRules) !== null && _a !== void 0 ? _a : defaultOptions.customRules,
|
|
76
|
-
};
|
|
77
|
-
const results = [];
|
|
78
|
-
const activeRules = [];
|
|
79
|
-
for (const name in opts.rules) {
|
|
80
|
-
const enabled = opts.rules[name];
|
|
81
|
-
if (enabled && builtInRules[name]) {
|
|
82
|
-
activeRules.push(builtInRules[name]());
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if (opts.customRules)
|
|
86
|
-
activeRules.push(...opts.customRules);
|
|
87
|
-
activeRules.forEach(r => r.init && r.init());
|
|
88
|
-
let ast = null;
|
|
89
|
-
try {
|
|
90
|
-
ast = (0, parser_1.parse)(content, {
|
|
91
|
-
sourceType: 'module',
|
|
92
|
-
plugins: ['typescript', 'jsx'],
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
ast = null;
|
|
97
|
-
}
|
|
98
|
-
if (ast) {
|
|
99
|
-
(0, traverse_1.default)(ast, {
|
|
100
|
-
JSXElement: {
|
|
101
|
-
enter(path) {
|
|
102
|
-
var _a;
|
|
103
|
-
for (const rule of activeRules) {
|
|
104
|
-
if (rule.enterJsx) {
|
|
105
|
-
try {
|
|
106
|
-
results.push(...rule.enterJsx(path));
|
|
107
|
-
}
|
|
108
|
-
catch (e) {
|
|
109
|
-
console.error(`[ZemDomu] Error in rule ${rule.name} (${(_a = opts.filePath) !== null && _a !== void 0 ? _a : 'unknown'}):`, e);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
exit(path) {
|
|
115
|
-
var _a;
|
|
116
|
-
for (const rule of activeRules) {
|
|
117
|
-
if (rule.exitJsx) {
|
|
118
|
-
try {
|
|
119
|
-
results.push(...rule.exitJsx(path));
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
122
|
-
console.error(`[ZemDomu] Error in rule ${rule.name} (${(_a = opts.filePath) !== null && _a !== void 0 ? _a : 'unknown'}):`, e);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
activeRules.forEach(r => r.end && results.push(...r.end()));
|
|
130
|
-
return results;
|
|
131
|
-
}
|
|
132
|
-
const root = (0, simpleHtmlParser_1.parse)(content);
|
|
133
|
-
const walk = (node) => {
|
|
134
|
-
var _a, _b;
|
|
135
|
-
for (const rule of activeRules) {
|
|
136
|
-
if (rule.enterHtml) {
|
|
137
|
-
try {
|
|
138
|
-
results.push(...rule.enterHtml(node));
|
|
139
|
-
}
|
|
140
|
-
catch (e) {
|
|
141
|
-
console.error(`[ZemDomu] Error in rule ${rule.name} (${(_a = opts.filePath) !== null && _a !== void 0 ? _a : 'unknown'}):`, e);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (node.children) {
|
|
146
|
-
for (const child of node.children) {
|
|
147
|
-
walk(child);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
for (const rule of activeRules) {
|
|
151
|
-
if (rule.exitHtml) {
|
|
152
|
-
try {
|
|
153
|
-
results.push(...rule.exitHtml(node));
|
|
154
|
-
}
|
|
155
|
-
catch (e) {
|
|
156
|
-
console.error(`[ZemDomu] Error in rule ${rule.name} (${(_b = opts.filePath) !== null && _b !== void 0 ? _b : 'unknown'}):`, e);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
walk(root);
|
|
162
|
-
activeRules.forEach(r => r.end && results.push(...r.end()));
|
|
163
|
-
return results;
|
|
164
|
-
}
|
package/out/src/cli.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const glob_1 = __importDefault(require("glob"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const project_linter_1 = require("./project-linter");
|
|
10
|
+
function parsePatterns(inputs) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for (const input of inputs) {
|
|
13
|
+
const splits = input
|
|
14
|
+
.split(/\r?\n/)
|
|
15
|
+
.flatMap((p) => p.split(/[ ,]+/))
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
result.push(...splits);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
async function run() {
|
|
22
|
+
var _a;
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const rawPatterns = [];
|
|
25
|
+
const customRules = [];
|
|
26
|
+
let cross = false;
|
|
27
|
+
let depth;
|
|
28
|
+
for (let i = 0; i < args.length; i++) {
|
|
29
|
+
const arg = args[i];
|
|
30
|
+
if (arg === '--custom' || arg === '-c') {
|
|
31
|
+
const file = args[++i];
|
|
32
|
+
if (!file)
|
|
33
|
+
throw new Error('Missing file for --custom');
|
|
34
|
+
const resolved = path_1.default.resolve(file);
|
|
35
|
+
const customDir = path_1.default.resolve('custom-rules');
|
|
36
|
+
const relative = path_1.default.relative(customDir, resolved);
|
|
37
|
+
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
|
38
|
+
throw new Error('Custom rule file must be inside ./custom-rules');
|
|
39
|
+
}
|
|
40
|
+
const mod = require(resolved);
|
|
41
|
+
const rules = (_a = mod.default) !== null && _a !== void 0 ? _a : mod;
|
|
42
|
+
if (Array.isArray(rules))
|
|
43
|
+
customRules.push(...rules);
|
|
44
|
+
else
|
|
45
|
+
customRules.push(rules);
|
|
46
|
+
}
|
|
47
|
+
else if (arg === '--cross') {
|
|
48
|
+
cross = true;
|
|
49
|
+
}
|
|
50
|
+
else if (arg === '--cross-depth') {
|
|
51
|
+
const val = args[++i];
|
|
52
|
+
if (!val)
|
|
53
|
+
throw new Error('Missing value for --cross-depth');
|
|
54
|
+
depth = parseInt(val, 10);
|
|
55
|
+
if (isNaN(depth))
|
|
56
|
+
throw new Error('Invalid number for --cross-depth');
|
|
57
|
+
cross = true;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
rawPatterns.push(arg);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const patterns = parsePatterns(rawPatterns);
|
|
64
|
+
if (patterns.length === 0) {
|
|
65
|
+
patterns.push('**/*.{html,jsx,tsx}');
|
|
66
|
+
}
|
|
67
|
+
const files = new Set();
|
|
68
|
+
for (const pattern of patterns) {
|
|
69
|
+
const matches = glob_1.default.sync(pattern, { nodir: true });
|
|
70
|
+
for (const m of matches)
|
|
71
|
+
files.add(m);
|
|
72
|
+
}
|
|
73
|
+
const linter = new project_linter_1.ProjectLinter({ customRules, crossComponentAnalysis: cross, crossComponentDepth: depth });
|
|
74
|
+
const results = await linter.lintFiles(Array.from(files));
|
|
75
|
+
let hasIssues = false;
|
|
76
|
+
for (const [file, issues] of results.entries()) {
|
|
77
|
+
for (const issue of issues) {
|
|
78
|
+
console.error(`${file}:${issue.line + 1}:${issue.column + 1} ${issue.rule}: ${issue.message}`);
|
|
79
|
+
hasIssues = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (hasIssues)
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
run().catch((e) => {
|
|
86
|
+
console.error(e);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|