zemdomu 1.1.4 → 1.2.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 +224 -112
- package/out/src/cli.js +82 -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} +87 -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/tests/cli-custom-rule.test.js +24 -0
- package/out/tests/cross-duplicate-ids-tsx.test.js +36 -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 +54 -34
- package/out/index.js +0 -12
- package/out/linter.js +0 -164
- 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,112 +1,224 @@
|
|
|
1
|
-
ZemDomu
|
|
2
|
-
|
|
3
|
-
Semantic HTML linting engine for clean, accessible
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
📖 API
|
|
53
|
-
|
|
54
|
-
lint(content: string, options?: LinterOptions): LintResult[]
|
|
55
|
-
|
|
56
|
-
Parameters
|
|
57
|
-
|
|
58
|
-
content — HTML, JSX
|
|
59
|
-
|
|
60
|
-
options.
|
|
61
|
-
options.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
1
|
+
# ZemDomu Core
|
|
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/src/cli.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
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 mod = require(path_1.default.resolve(file));
|
|
35
|
+
const rules = (_a = mod.default) !== null && _a !== void 0 ? _a : mod;
|
|
36
|
+
if (Array.isArray(rules))
|
|
37
|
+
customRules.push(...rules);
|
|
38
|
+
else
|
|
39
|
+
customRules.push(rules);
|
|
40
|
+
}
|
|
41
|
+
else if (arg === '--cross') {
|
|
42
|
+
cross = true;
|
|
43
|
+
}
|
|
44
|
+
else if (arg === '--cross-depth') {
|
|
45
|
+
const val = args[++i];
|
|
46
|
+
if (!val)
|
|
47
|
+
throw new Error('Missing value for --cross-depth');
|
|
48
|
+
depth = parseInt(val, 10);
|
|
49
|
+
if (isNaN(depth))
|
|
50
|
+
throw new Error('Invalid number for --cross-depth');
|
|
51
|
+
cross = true;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
rawPatterns.push(arg);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const patterns = parsePatterns(rawPatterns);
|
|
58
|
+
if (patterns.length === 0) {
|
|
59
|
+
patterns.push('**/*.{html,jsx,tsx}');
|
|
60
|
+
}
|
|
61
|
+
const files = new Set();
|
|
62
|
+
for (const pattern of patterns) {
|
|
63
|
+
const matches = glob_1.default.sync(pattern, { nodir: true });
|
|
64
|
+
for (const m of matches)
|
|
65
|
+
files.add(m);
|
|
66
|
+
}
|
|
67
|
+
const linter = new project_linter_1.ProjectLinter({ customRules, crossComponentAnalysis: cross, crossComponentDepth: depth });
|
|
68
|
+
const results = await linter.lintFiles(Array.from(files));
|
|
69
|
+
let hasIssues = false;
|
|
70
|
+
for (const [file, issues] of results.entries()) {
|
|
71
|
+
for (const issue of issues) {
|
|
72
|
+
console.error(`${file}:${issue.line + 1}:${issue.column + 1} ${issue.rule}: ${issue.message}`);
|
|
73
|
+
hasIssues = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (hasIssues)
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
run().catch((e) => {
|
|
80
|
+
console.error(e);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|