zemdomu 1.2.0 โ 1.3.2
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 +224 -224
- package/out/linter.js +0 -0
- package/out/src/cli.js +7 -1
- package/out/src/project-linter.js +31 -4
- package/out/src/utils/collectLocalDeps.js +132 -0
- package/out/tests/cli-custom-rule.test.js +20 -2
- 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/package.json +60 -54
package/README.md
CHANGED
|
@@ -1,224 +1,224 @@
|
|
|
1
|
-
# ZemDomu
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
## ๐ง What is ZemDomu?
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
## ๐ Installation
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install zemdomu
|
|
13
|
-
# or
|
|
14
|
-
yarn add zemdomu
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## โจ Features
|
|
18
|
-
|
|
19
|
-
- โ
Lint semantic issues in HTML, JSX and TSX
|
|
20
|
-
- ๐ฆ Works in Node.js, CI or any JS runtime
|
|
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
|
|
26
|
-
- ๐ Shared by the extension and GitHub Action
|
|
27
|
-
- ๐งช Simple API: `lint(content, options)`
|
|
28
|
-
|
|
29
|
-
## โ๏ธ Usage Example
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import { lint } from "zemdomu";
|
|
33
|
-
|
|
34
|
-
const html = "<img>";
|
|
35
|
-
const results = lint(html, { rules: { requireAltText: true } });
|
|
36
|
-
|
|
37
|
-
console.log(results);
|
|
38
|
-
// [
|
|
39
|
-
// {
|
|
40
|
-
// line: 0,
|
|
41
|
-
// column: 0,
|
|
42
|
-
// message: '<img> tag missing alt attribute',
|
|
43
|
-
// rule: 'requireAltText'
|
|
44
|
-
// }
|
|
45
|
-
// ]
|
|
46
|
-
|
|
47
|
-
// Custom rules can be supplied via the `customRules` option
|
|
48
|
-
// const myRule = { name: 'demo', test: node => false, message: 'demo' };
|
|
49
|
-
// lint(html, { customRules: [myRule] });
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## ๐ API
|
|
53
|
-
|
|
54
|
-
`lint(content: string, options?: LinterOptions): LintResult[]`
|
|
55
|
-
|
|
56
|
-
**Parameters**
|
|
57
|
-
|
|
58
|
-
- `content` โ HTML, JSX or TSX string input
|
|
59
|
-
- `options.rules` โ severity settings for built-in rules
|
|
60
|
-
- `options.customRules` โ array of additional rules
|
|
61
|
-
- `options.filePath` โ optional source file path
|
|
62
|
-
- `options.perf` โ attach a `PerformanceRecorder` instance
|
|
63
|
-
|
|
64
|
-
**Example `LinterOptions`**
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
interface LinterOptions {
|
|
68
|
-
rules?: Record<string, 'error' | 'warning' | 'off'>;
|
|
69
|
-
customRules?: Rule[];
|
|
70
|
-
filePath?: string;
|
|
71
|
-
perf?: PerformanceRecorder;
|
|
72
|
-
}
|
|
73
|
-
```
|
|
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
|
-
|
|
83
|
-
**Example `LintResult`**
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
interface LintResult {
|
|
87
|
-
line: number;
|
|
88
|
-
column: number;
|
|
89
|
-
message: string;
|
|
90
|
-
rule: string;
|
|
91
|
-
}
|
|
92
|
-
```
|
|
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
|
-
|
|
197
|
-
## ๐ Related Tools
|
|
198
|
-
|
|
199
|
-
- [ZemDomu VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ZachariasErydBerlin.zemdomu)
|
|
200
|
-
- ZemDomu GitHub Action (coming soon)
|
|
201
|
-
|
|
202
|
-
## ๐ Development
|
|
203
|
-
|
|
204
|
-
```bash
|
|
205
|
-
git clone https://github.com/Zemdomu/ZemDomu-core.git
|
|
206
|
-
cd ZemDomu-core
|
|
207
|
-
npm install
|
|
208
|
-
npm run build
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Tests and coverage support coming soon.
|
|
212
|
-
|
|
213
|
-
## ๐ค Contributing
|
|
214
|
-
|
|
215
|
-
We welcome contributions! If you'd like to add rules, improve parsing or integrate new consumers:
|
|
216
|
-
|
|
217
|
-
1. Fork this repo
|
|
218
|
-
2. Add your logic inside `src/rules` or `src/linter.ts`
|
|
219
|
-
3. Write or update tests (if applicable)
|
|
220
|
-
4. Submit a pull request!
|
|
221
|
-
|
|
222
|
-
## ๐ License
|
|
223
|
-
|
|
224
|
-
MIT ยฉ 2025 Zacharias Eryd Berlin
|
|
1
|
+
# ZemDomu
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## ๐ง What is ZemDomu?
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
## ๐ Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install zemdomu
|
|
13
|
+
# or
|
|
14
|
+
yarn add zemdomu
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## โจ Features
|
|
18
|
+
|
|
19
|
+
- โ
Lint semantic issues in HTML, JSX and TSX
|
|
20
|
+
- ๐ฆ Works in Node.js, CI or any JS runtime
|
|
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
|
|
26
|
+
- ๐ Shared by the extension and GitHub Action
|
|
27
|
+
- ๐งช Simple API: `lint(content, options)`
|
|
28
|
+
|
|
29
|
+
## โ๏ธ Usage Example
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { lint } from "zemdomu";
|
|
33
|
+
|
|
34
|
+
const html = "<img>";
|
|
35
|
+
const results = lint(html, { rules: { requireAltText: true } });
|
|
36
|
+
|
|
37
|
+
console.log(results);
|
|
38
|
+
// [
|
|
39
|
+
// {
|
|
40
|
+
// line: 0,
|
|
41
|
+
// column: 0,
|
|
42
|
+
// message: '<img> tag missing alt attribute',
|
|
43
|
+
// rule: 'requireAltText'
|
|
44
|
+
// }
|
|
45
|
+
// ]
|
|
46
|
+
|
|
47
|
+
// Custom rules can be supplied via the `customRules` option
|
|
48
|
+
// const myRule = { name: 'demo', test: node => false, message: 'demo' };
|
|
49
|
+
// lint(html, { customRules: [myRule] });
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## ๐ API
|
|
53
|
+
|
|
54
|
+
`lint(content: string, options?: LinterOptions): LintResult[]`
|
|
55
|
+
|
|
56
|
+
**Parameters**
|
|
57
|
+
|
|
58
|
+
- `content` โ HTML, JSX or TSX string input
|
|
59
|
+
- `options.rules` โ severity settings for built-in rules
|
|
60
|
+
- `options.customRules` โ array of additional rules
|
|
61
|
+
- `options.filePath` โ optional source file path
|
|
62
|
+
- `options.perf` โ attach a `PerformanceRecorder` instance
|
|
63
|
+
|
|
64
|
+
**Example `LinterOptions`**
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
interface LinterOptions {
|
|
68
|
+
rules?: Record<string, 'error' | 'warning' | 'off'>;
|
|
69
|
+
customRules?: Rule[];
|
|
70
|
+
filePath?: string;
|
|
71
|
+
perf?: PerformanceRecorder;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
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
|
+
|
|
83
|
+
**Example `LintResult`**
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
interface LintResult {
|
|
87
|
+
line: number;
|
|
88
|
+
column: number;
|
|
89
|
+
message: string;
|
|
90
|
+
rule: string;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
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
|
+
|
|
197
|
+
## ๐ Related Tools
|
|
198
|
+
|
|
199
|
+
- [ZemDomu VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ZachariasErydBerlin.zemdomu)
|
|
200
|
+
- ZemDomu GitHub Action (coming soon)
|
|
201
|
+
|
|
202
|
+
## ๐ Development
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
git clone https://github.com/Zemdomu/ZemDomu-core.git
|
|
206
|
+
cd ZemDomu-core
|
|
207
|
+
npm install
|
|
208
|
+
npm run build
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Tests and coverage support coming soon.
|
|
212
|
+
|
|
213
|
+
## ๐ค Contributing
|
|
214
|
+
|
|
215
|
+
We welcome contributions! If you'd like to add rules, improve parsing or integrate new consumers:
|
|
216
|
+
|
|
217
|
+
1. Fork this repo
|
|
218
|
+
2. Add your logic inside `src/rules` or `src/linter.ts`
|
|
219
|
+
3. Write or update tests (if applicable)
|
|
220
|
+
4. Submit a pull request!
|
|
221
|
+
|
|
222
|
+
## ๐ License
|
|
223
|
+
|
|
224
|
+
MIT ยฉ 2025 Zacharias Eryd Berlin
|
package/out/linter.js
ADDED
|
File without changes
|
package/out/src/cli.js
CHANGED
|
@@ -31,7 +31,13 @@ async function run() {
|
|
|
31
31
|
const file = args[++i];
|
|
32
32
|
if (!file)
|
|
33
33
|
throw new Error('Missing file for --custom');
|
|
34
|
-
const
|
|
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);
|
|
35
41
|
const rules = (_a = mod.default) !== null && _a !== void 0 ? _a : mod;
|
|
36
42
|
if (Array.isArray(rules))
|
|
37
43
|
customRules.push(...rules);
|
|
@@ -32,11 +32,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.ProjectLinter = void 0;
|
|
37
40
|
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const ts = __importStar(require("typescript"));
|
|
38
43
|
const linter_1 = require("./linter");
|
|
39
44
|
const component_analyzer_1 = require("./component-analyzer");
|
|
45
|
+
const collectLocalDeps_1 = require("./utils/collectLocalDeps");
|
|
40
46
|
class ProjectLinter {
|
|
41
47
|
constructor(options = {}) {
|
|
42
48
|
this.opts = options;
|
|
@@ -47,7 +53,7 @@ class ProjectLinter {
|
|
|
47
53
|
}
|
|
48
54
|
async lintFile(filePath, content) {
|
|
49
55
|
if (!content) {
|
|
50
|
-
content = await fs.readFile(filePath,
|
|
56
|
+
content = await fs.readFile(filePath, "utf8");
|
|
51
57
|
}
|
|
52
58
|
const results = (0, linter_1.lint)(content, { ...this.opts, filePath });
|
|
53
59
|
const byFile = new Map();
|
|
@@ -55,9 +61,8 @@ class ProjectLinter {
|
|
|
55
61
|
const xmlMode = /\.(jsx|tsx)$/.test(filePath);
|
|
56
62
|
if (xmlMode) {
|
|
57
63
|
const component = await this.analyzer.analyzeFile(filePath);
|
|
58
|
-
if (component)
|
|
64
|
+
if (component)
|
|
59
65
|
this.analyzer.registerComponent(component, results);
|
|
60
|
-
}
|
|
61
66
|
if (this.opts.crossComponentAnalysis) {
|
|
62
67
|
const cross = this.analyzer.analyzeComponentTree();
|
|
63
68
|
for (const r of cross) {
|
|
@@ -72,8 +77,30 @@ class ProjectLinter {
|
|
|
72
77
|
return byFile;
|
|
73
78
|
}
|
|
74
79
|
async lintFiles(filePaths) {
|
|
80
|
+
var _a, _b, _c;
|
|
81
|
+
const root = (_a = this.opts.rootDir) !== null && _a !== void 0 ? _a : process.cwd();
|
|
82
|
+
const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
83
|
+
let baseUrl;
|
|
84
|
+
let paths;
|
|
85
|
+
if (configPath) {
|
|
86
|
+
const cfg = ts.readConfigFile(configPath, ts.sys.readFile).config;
|
|
87
|
+
const cfgDir = path_1.default.dirname(configPath);
|
|
88
|
+
baseUrl = ((_b = cfg === null || cfg === void 0 ? void 0 : cfg.compilerOptions) === null || _b === void 0 ? void 0 : _b.baseUrl)
|
|
89
|
+
? path_1.default.resolve(cfgDir, cfg.compilerOptions.baseUrl)
|
|
90
|
+
: undefined;
|
|
91
|
+
paths = (_c = cfg === null || cfg === void 0 ? void 0 : cfg.compilerOptions) === null || _c === void 0 ? void 0 : _c.paths;
|
|
92
|
+
}
|
|
93
|
+
const targets = this.opts.crossComponentAnalysis
|
|
94
|
+
? (0, collectLocalDeps_1.collectLocalDeps)(filePaths, {
|
|
95
|
+
rootDir: root,
|
|
96
|
+
baseUrl,
|
|
97
|
+
paths,
|
|
98
|
+
maxDepth: this.opts.crossComponentDepth,
|
|
99
|
+
})
|
|
100
|
+
: filePaths;
|
|
101
|
+
const uniqueTargets = Array.from(new Set(targets.map((p) => path_1.default.resolve(p))));
|
|
75
102
|
const aggregated = new Map();
|
|
76
|
-
for (const filePath of
|
|
103
|
+
for (const filePath of uniqueTargets) {
|
|
77
104
|
const fileMap = await this.lintFile(filePath);
|
|
78
105
|
for (const [fp, res] of fileMap.entries()) {
|
|
79
106
|
if (!aggregated.has(fp))
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.collectLocalDeps = collectLocalDeps;
|
|
40
|
+
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const ts = __importStar(require("typescript"));
|
|
43
|
+
const EXTS = [".tsx", ".ts", ".jsx", ".js"];
|
|
44
|
+
function resolveWithExtensions(base) {
|
|
45
|
+
if (fs_1.default.existsSync(base) && fs_1.default.statSync(base).isFile())
|
|
46
|
+
return base;
|
|
47
|
+
for (const ext of EXTS) {
|
|
48
|
+
const p = base + ext;
|
|
49
|
+
if (fs_1.default.existsSync(p) && fs_1.default.statSync(p).isFile())
|
|
50
|
+
return p;
|
|
51
|
+
}
|
|
52
|
+
for (const ext of EXTS) {
|
|
53
|
+
const p = path_1.default.join(base, "index" + ext);
|
|
54
|
+
if (fs_1.default.existsSync(p) && fs_1.default.statSync(p).isFile())
|
|
55
|
+
return p;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
function resolveAlias(spec, fileDir, ctx) {
|
|
60
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
61
|
+
return resolveWithExtensions(path_1.default.resolve(fileDir, spec));
|
|
62
|
+
}
|
|
63
|
+
const { baseUrl, paths } = ctx;
|
|
64
|
+
if (baseUrl && paths) {
|
|
65
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
66
|
+
const starIdx = pattern.indexOf("*");
|
|
67
|
+
if (starIdx >= 0) {
|
|
68
|
+
const pre = pattern.slice(0, starIdx);
|
|
69
|
+
const post = pattern.slice(starIdx + 1);
|
|
70
|
+
if (spec.startsWith(pre) && spec.endsWith(post)) {
|
|
71
|
+
const middle = spec.slice(pre.length, spec.length - post.length);
|
|
72
|
+
for (const t of targets) {
|
|
73
|
+
const candidate = t.replace("*", middle);
|
|
74
|
+
const abs = path_1.default.resolve(baseUrl, candidate);
|
|
75
|
+
const hit = resolveWithExtensions(abs);
|
|
76
|
+
if (hit)
|
|
77
|
+
return hit;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (spec === pattern) {
|
|
82
|
+
for (const t of targets) {
|
|
83
|
+
const abs = path_1.default.resolve(baseUrl, t);
|
|
84
|
+
const hit = resolveWithExtensions(abs);
|
|
85
|
+
if (hit)
|
|
86
|
+
return hit;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function collectLocalDeps(entries, ctx) {
|
|
94
|
+
const root = path_1.default.resolve(ctx.rootDir);
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
const q = entries.map((p) => ({
|
|
97
|
+
file: path_1.default.resolve(p),
|
|
98
|
+
depth: 0,
|
|
99
|
+
}));
|
|
100
|
+
while (q.length) {
|
|
101
|
+
const { file, depth } = q.pop();
|
|
102
|
+
if (seen.has(file))
|
|
103
|
+
continue;
|
|
104
|
+
seen.add(file);
|
|
105
|
+
if (ctx.maxDepth !== undefined && depth >= ctx.maxDepth)
|
|
106
|
+
continue;
|
|
107
|
+
let code = "";
|
|
108
|
+
try {
|
|
109
|
+
code = fs_1.default.readFileSync(file, "utf8");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const sf = ts.createSourceFile(file, code, ts.ScriptTarget.Latest, true);
|
|
115
|
+
sf.forEachChild((node) => {
|
|
116
|
+
let spec;
|
|
117
|
+
if (ts.isImportDeclaration(node) && node.moduleSpecifier) {
|
|
118
|
+
spec = node.moduleSpecifier.text;
|
|
119
|
+
}
|
|
120
|
+
else if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
|
|
121
|
+
spec = node.moduleSpecifier.text;
|
|
122
|
+
}
|
|
123
|
+
if (!spec)
|
|
124
|
+
return;
|
|
125
|
+
const resolved = resolveAlias(spec, path_1.default.dirname(file), ctx);
|
|
126
|
+
if (resolved && resolved.startsWith(root)) {
|
|
127
|
+
q.push({ file: resolved, depth: depth + 1 });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return Array.from(seen);
|
|
132
|
+
}
|
|
@@ -6,11 +6,14 @@ const { spawnSync } = require('child_process');
|
|
|
6
6
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'zd-cli-'));
|
|
7
7
|
const htmlFile = path.join(tmp, 'test.html');
|
|
8
8
|
fs.writeFileSync(htmlFile, '<foo></foo>');
|
|
9
|
-
const
|
|
9
|
+
const customDir = path.join(tmp, 'custom-rules');
|
|
10
|
+
fs.mkdirSync(customDir);
|
|
11
|
+
const ruleFile = path.join(customDir, 'my-rule.js');
|
|
10
12
|
fs.writeFileSync(ruleFile, `module.exports = {\n name: 'noFoo',\n test: n => (n.type === 'element' && n.tagName === 'foo') || (n.type === 'JSXElement' && n.openingElement && n.openingElement.name && n.openingElement.name.name === 'foo'),\n message: 'Foo elements are not allowed'\n};\n`);
|
|
11
13
|
const cli = path.join(__dirname, '..', 'src', 'cli.js');
|
|
12
14
|
const result = spawnSync('node', [cli, htmlFile, '--custom', ruleFile], {
|
|
13
|
-
encoding: 'utf8'
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
cwd: tmp,
|
|
14
17
|
});
|
|
15
18
|
if (result.status === 0) {
|
|
16
19
|
console.error(result.stdout, result.stderr);
|
|
@@ -22,3 +25,18 @@ if (!output.includes('Foo elements are not allowed')) {
|
|
|
22
25
|
throw new Error('Expected custom rule message');
|
|
23
26
|
}
|
|
24
27
|
console.log('CLI custom rule test passed');
|
|
28
|
+
// Should error when rule file is outside the custom-rules directory
|
|
29
|
+
const outsideRule = path.join(tmp, 'other-rule.js');
|
|
30
|
+
fs.writeFileSync(outsideRule, 'module.exports = {};');
|
|
31
|
+
const bad = spawnSync('node', [cli, htmlFile, '--custom', outsideRule], {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
cwd: tmp,
|
|
34
|
+
});
|
|
35
|
+
if (bad.status === 0) {
|
|
36
|
+
throw new Error('Expected CLI to fail for outside rule');
|
|
37
|
+
}
|
|
38
|
+
if (!bad.stderr.includes('Custom rule file must be inside ./custom-rules')) {
|
|
39
|
+
console.error(bad.stdout, bad.stderr);
|
|
40
|
+
throw new Error('Expected directory restriction error');
|
|
41
|
+
}
|
|
42
|
+
console.log('CLI custom rule path restriction test passed');
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = Button;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
function Button() {
|
|
6
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { children: "Button" }), (0, jsx_runtime_1.jsx)("h5", { children: "Subsection" })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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.default = Page;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const Section_1 = __importDefault(require("@alias/Section"));
|
|
9
|
+
function Page() {
|
|
10
|
+
return ((0, jsx_runtime_1.jsxs)("main", { children: [(0, jsx_runtime_1.jsx)("h1", { children: "Hello" }), (0, jsx_runtime_1.jsx)(Section_1.default, {})] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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.default = Section;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const SubSection_1 = __importDefault(require("@alias/SubSection"));
|
|
9
|
+
function Section() {
|
|
10
|
+
return ((0, jsx_runtime_1.jsxs)("section", { children: [(0, jsx_runtime_1.jsx)("h2", { children: "Section Title" }), (0, jsx_runtime_1.jsx)(SubSection_1.default, {})] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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.default = SubSection;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const Button_1 = __importDefault(require("@alias/Button"));
|
|
9
|
+
function SubSection() {
|
|
10
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h3", { children: "SubSection" }), (0, jsx_runtime_1.jsx)(Button_1.default, {})] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
const assert_1 = __importDefault(require("assert"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const index_1 = require("../../src/index");
|
|
9
|
+
describe("cross component heading order (ts-path-alias)", () => {
|
|
10
|
+
it("follows @alias/* from PageAlias and finds violations", async () => {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const pagePath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/Page.tsx");
|
|
13
|
+
const linter = new index_1.ProjectLinter({
|
|
14
|
+
crossComponentAnalysis: true,
|
|
15
|
+
rules: {
|
|
16
|
+
singleH1: "error",
|
|
17
|
+
enforceHeadingOrder: "error",
|
|
18
|
+
requireButtonText: "off",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const map = await linter.lintFiles([pagePath]);
|
|
22
|
+
const results = Array.from(map.values()).flat();
|
|
23
|
+
const byRule = results.reduce((a, r) => {
|
|
24
|
+
var _a;
|
|
25
|
+
a[r.rule] = ((_a = a[r.rule]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
26
|
+
return a;
|
|
27
|
+
}, {});
|
|
28
|
+
assert_1.default.ok(((_a = byRule["singleH1"]) !== null && _a !== void 0 ? _a : 0) >= 1);
|
|
29
|
+
assert_1.default.ok(((_b = byRule["enforceHeadingOrder"]) !== null && _b !== void 0 ? _b : 0) >= 1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
const assert_1 = __importDefault(require("assert"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const index_1 = require("../../src/index");
|
|
9
|
+
describe("cross component heading order (entry-only)", () => {
|
|
10
|
+
it("follows imports from Page and finds violations", async () => {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const pagePath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/Page.tsx");
|
|
13
|
+
const linter = new index_1.ProjectLinter({
|
|
14
|
+
crossComponentAnalysis: true,
|
|
15
|
+
rules: {
|
|
16
|
+
singleH1: "error",
|
|
17
|
+
enforceHeadingOrder: "error",
|
|
18
|
+
requireButtonText: "off",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const map = await linter.lintFiles([pagePath]);
|
|
22
|
+
const results = Array.from(map.values()).flat();
|
|
23
|
+
const byRule = results.reduce((a, r) => {
|
|
24
|
+
var _a;
|
|
25
|
+
a[r.rule] = ((_a = a[r.rule]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
26
|
+
return a;
|
|
27
|
+
}, {});
|
|
28
|
+
assert_1.default.ok(((_a = byRule["singleH1"]) !== null && _a !== void 0 ? _a : 0) >= 1);
|
|
29
|
+
assert_1.default.ok(((_b = byRule["enforceHeadingOrder"]) !== null && _b !== void 0 ? _b : 0) >= 1);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
// tests/crossComponent/cross-heading-order.test.ts
|
|
7
|
+
const assert_1 = __importDefault(require("assert"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const index_1 = require("../../src/index");
|
|
10
|
+
describe("cross component heading order", () => {
|
|
11
|
+
it("detects heading order and h1 issues across components", async () => {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
const buttonPath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/Button.tsx");
|
|
14
|
+
const sectionPath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/Section.tsx");
|
|
15
|
+
const subSectionPath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/SubSection.tsx");
|
|
16
|
+
const pagePath = path_1.default.resolve(__dirname, "../../../tests/crossComponent/Page.tsx");
|
|
17
|
+
const linter = new index_1.ProjectLinter({
|
|
18
|
+
crossComponentAnalysis: true,
|
|
19
|
+
rules: {
|
|
20
|
+
singleH1: "error",
|
|
21
|
+
enforceHeadingOrder: "error",
|
|
22
|
+
requireButtonText: "off",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
const map = await linter.lintFiles([
|
|
26
|
+
buttonPath,
|
|
27
|
+
sectionPath,
|
|
28
|
+
subSectionPath,
|
|
29
|
+
pagePath,
|
|
30
|
+
]);
|
|
31
|
+
const results = Array.from(map.values()).flat();
|
|
32
|
+
// Presence checks
|
|
33
|
+
assert_1.default.ok(results.some((r) => r.rule === "singleH1"), "Expected cross-component singleH1 warning");
|
|
34
|
+
assert_1.default.ok(results.some((r) => r.rule === "enforceHeadingOrder"), "Expected cross-component heading order warning");
|
|
35
|
+
// Count checks (looser; tighten later if you want)
|
|
36
|
+
const byRule = results.reduce((acc, r) => {
|
|
37
|
+
var _a;
|
|
38
|
+
acc[r.rule] = ((_a = acc[r.rule]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
39
|
+
return acc;
|
|
40
|
+
}, {});
|
|
41
|
+
assert_1.default.ok(((_a = byRule["singleH1"]) !== null && _a !== void 0 ? _a : 0) >= 1, "Expected at least one singleH1");
|
|
42
|
+
assert_1.default.ok(((_b = byRule["enforceHeadingOrder"]) !== null && _b !== void 0 ? _b : 0) >= 1, // set to >=2 if you expect more
|
|
43
|
+
"Expected at least one enforceHeadingOrder");
|
|
44
|
+
});
|
|
45
|
+
});
|
package/package.json
CHANGED
|
@@ -1,54 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "zemdomu",
|
|
3
|
-
"version": "1.2
|
|
4
|
-
"description": "Semantic HTML linter for HTML, JSX, and TSX. Detects accessibility, SEO, and structure issues before deployment.",
|
|
5
|
-
"main": "./out/index.js",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"@types/
|
|
36
|
-
"@types/
|
|
37
|
-
"@types/
|
|
38
|
-
"@types/
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "zemdomu",
|
|
3
|
+
"version": "1.3.2",
|
|
4
|
+
"description": "Semantic HTML linter for HTML, JSX, and TSX. Detects accessibility, SEO, and structure issues before deployment.",
|
|
5
|
+
"main": "./out/index.js",
|
|
6
|
+
"types": "./out/src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"zemdomu": "./out/src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"out"
|
|
12
|
+
],
|
|
13
|
+
"private": false,
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"compile": "tsc -p tsconfig.json",
|
|
17
|
+
"test": "npm run compile && mocha out/**/*.test.js",
|
|
18
|
+
"prepublishOnly": "npm run compile && npm test"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"html",
|
|
22
|
+
"jsx",
|
|
23
|
+
"tsx",
|
|
24
|
+
"linter",
|
|
25
|
+
"accessibility",
|
|
26
|
+
"semantic-html",
|
|
27
|
+
"seo",
|
|
28
|
+
"cli",
|
|
29
|
+
"vscode",
|
|
30
|
+
"github-action"
|
|
31
|
+
],
|
|
32
|
+
"author": "Zacharias Eryd Berlin",
|
|
33
|
+
"license": "ISC",
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/babel__traverse": "^7.20.7",
|
|
36
|
+
"@types/babel-traverse": "^6.25.10",
|
|
37
|
+
"@types/babel-types": "^7.0.16",
|
|
38
|
+
"@types/glob": "^8.1.0",
|
|
39
|
+
"@types/mocha": "^10.0.10",
|
|
40
|
+
"@types/node": "^22.15.17",
|
|
41
|
+
"@types/react": "^19.1.9",
|
|
42
|
+
"@types/react-dom": "^19.1.7",
|
|
43
|
+
"esbuild": "^0.25.5",
|
|
44
|
+
"mocha": "^10.8.2",
|
|
45
|
+
"typescript": "^5.8.2"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@babel/parser": "^7.27.0",
|
|
49
|
+
"@babel/traverse": "^7.27.0",
|
|
50
|
+
"@babel/types": "^7.27.0",
|
|
51
|
+
"glob": "^7.2.3",
|
|
52
|
+
"react": "^19.1.1",
|
|
53
|
+
"react-dom": "^19.1.1"
|
|
54
|
+
},
|
|
55
|
+
"jest": {
|
|
56
|
+
"transform": {
|
|
57
|
+
"^.+\\.tsx?$": "ts-jest"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|