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.
Files changed (46) hide show
  1. package/README.md +224 -112
  2. package/out/src/cli.js +82 -0
  3. package/out/{component-analyzer.js → src/component-analyzer.js} +620 -461
  4. package/out/src/html-visitor.js +13 -0
  5. package/out/src/index.js +25 -0
  6. package/out/src/linter.js +272 -0
  7. package/out/src/performance-diagnostics.js +47 -0
  8. package/out/{project-linter.js → src/project-linter.js} +87 -87
  9. package/out/{rules → src/rules}/enforceListNesting.js +81 -81
  10. package/out/{rules → src/rules}/noTabindexGreaterThanZero.js +33 -33
  11. package/out/{rules → src/rules}/preventEmptyInlineTags.js +85 -85
  12. package/out/{rules → src/rules}/requireLinkText.js +101 -86
  13. package/out/src/rules/requireNavLinks.js +88 -0
  14. package/out/src/sarif.js +54 -0
  15. package/out/{simpleHtmlParser.js → src/simpleHtmlParser.js} +4 -0
  16. package/out/tests/cli-custom-rule.test.js +24 -0
  17. package/out/tests/cross-duplicate-ids-tsx.test.js +36 -0
  18. package/out/tests/custom-rule-tsx.test.js +24 -0
  19. package/out/tests/edge-cases.test.js +17 -0
  20. package/out/tests/html-visitor.test.js +29 -0
  21. package/out/tests/linter.test.js +41 -0
  22. package/out/tests/parse-error.test.js +11 -0
  23. package/out/tests/performance-diagnostics.test.js +12 -0
  24. package/out/tests/sarif-output.test.js +15 -0
  25. package/out/tests/tsx-parse-error.test.js +11 -0
  26. package/out/tests/unique-ids-html.test.js +19 -0
  27. package/out/tests/unique-ids-tsx.test.js +19 -0
  28. package/package.json +54 -34
  29. package/out/index.js +0 -12
  30. package/out/linter.js +0 -164
  31. package/out/rules/requireNavLinks.js +0 -48
  32. /package/out/{component-path-resolver.js → src/component-path-resolver.js} +0 -0
  33. /package/out/{rules → src/rules}/enforceHeadingOrder.js +0 -0
  34. /package/out/{rules → src/rules}/requireAltText.js +0 -0
  35. /package/out/{rules → src/rules}/requireAltTextJSX.js +0 -0
  36. /package/out/{rules → src/rules}/requireButtonText.js +0 -0
  37. /package/out/{rules → src/rules}/requireHrefOnAnchors.js +0 -0
  38. /package/out/{rules → src/rules}/requireHtmlLang.js +0 -0
  39. /package/out/{rules → src/rules}/requireIframeTitle.js +0 -0
  40. /package/out/{rules → src/rules}/requireImageInputAlt.js +0 -0
  41. /package/out/{rules → src/rules}/requireLabelForFormControls.js +0 -0
  42. /package/out/{rules → src/rules}/requireSectionHeading.js +0 -0
  43. /package/out/{rules → src/rules}/requireTableCaption.js +0 -0
  44. /package/out/{rules → src/rules}/singleH1.js +0 -0
  45. /package/out/{rules → src/rules}/uniqueIds.js +0 -0
  46. /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, and SEO-friendly markup. This package provides the shared core logic used by the ZemDomu VS Code extension and upcoming GitHub Action.
4
-
5
-
6
-
7
- 🧠 What is ZemDomu Core?
8
-
9
- ZemDomu is a semantic-first linter that helps developers write better HTML and JSX by catching accessibility and structural issues. This package contains the framework-agnostic linting engine used by other tools in the ZemDomu ecosystem.
10
-
11
- It parses .html, .jsx, and .tsx content and exposes a simple lint() function that returns semantic violations.
12
-
13
- 🚀 Installation
14
-
15
- npm install zemdomu
16
- # or
17
- yarn add zemdomu
18
-
19
- Features
20
-
21
- Lint semantic issues in HTML, JSX, and TSX
22
-
23
- 📦 Works in Node.js, CI, or any JS runtime
24
-
25
- ⚙️ Extensible rule system
26
-
27
- 📚 Shared by extension and GitHub Action
28
-
29
- 🧪 Simple API: lint(content, options)
30
-
31
- ⚙️ Usage Example
32
-
33
- import { lint } from 'zemdomu';
34
-
35
- const html = '<img>';
36
- const results = lint(html, { rules: { requireAltText: true } });
37
-
38
- console.log(results);
39
- // [
40
- // {
41
- // line: 0,
42
- // column: 0,
43
- // message: '<img> tag missing alt attribute',
44
- // rule: 'requireAltText'
45
- // }
46
- // ]
47
-
48
- // Custom rules can be supplied via the `customRules` option
49
- // const myRule = { name: 'demo', checkHtml: () => [] };
50
- // lint(html, { customRules: [myRule] });
51
-
52
- 📖 API
53
-
54
- lint(content: string, options?: LinterOptions): LintResult[]
55
-
56
- Parameters:
57
-
58
- content — HTML, JSX, or TSX string input
59
-
60
- options.rulestoggles for built-in rules
61
- options.customRulesarray of additional rules
62
-
63
- Example LinterOptions
64
-
65
- interface LinterOptions {
66
- rules: {
67
- requireAltText: boolean;
68
- // ...more rules to come
69
- };
70
- customRules?: Rule[];
71
- }
72
-
73
- Example LintResult
74
-
75
- interface LintResult {
76
- line: number;
77
- column: number;
78
- message: string;
79
- rule: string;
80
- }
81
-
82
- 🔗 Related Tools
83
-
84
- ZemDomu VS Code Extension
85
-
86
- ZemDomu GitHub Action (coming soon)
87
-
88
- 🛠 Development
89
-
90
- git clone https://github.com/Zemdomu/ZemDomu-core.git
91
- cd ZemDomu-core
92
- npm install
93
- npm run build
94
-
95
- Tests and coverage support coming soon.
96
-
97
- 🤝 Contributing
98
-
99
- We welcome contributions! If you'd like to add rules, improve parsing, or integrate new consumers:
100
-
101
- Fork this repo
102
-
103
- Add your logic inside src/rules or src/linter.ts
104
-
105
- Write or update tests (if applicable)
106
-
107
- Submit a pull request!
108
-
109
- 📄 License
110
-
111
- MIT © 2025 Zacharias Eryd Berlin
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
+ });