zemdomu 1.0.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 +112 -0
- package/out/index.js +6 -0
- package/out/linter.js +138 -0
- package/out/rules/enforceHeadingOrder.js +39 -0
- package/out/rules/enforceListNesting.js +78 -0
- package/out/rules/preventEmptyInlineTags.js +83 -0
- package/out/rules/requireAltText.js +78 -0
- package/out/rules/requireAltTextJSX.js +63 -0
- package/out/rules/requireButtonText.js +78 -0
- package/out/rules/requireHrefOnAnchors.js +66 -0
- package/out/rules/requireHtmlLang.js +72 -0
- package/out/rules/requireIframeTitle.js +69 -0
- package/out/rules/requireImageInputAlt.js +69 -0
- package/out/rules/requireLabelForFormControls.js +84 -0
- package/out/rules/requireLinkText.js +86 -0
- package/out/rules/requireNavLinks.js +48 -0
- package/out/rules/requireSectionHeading.js +48 -0
- package/out/rules/requireTableCaption.js +48 -0
- package/out/rules/singleH1.js +33 -0
- package/out/rules/uniqueIds.js +33 -0
- package/out/rules/utils.js +50 -0
- package/out/simpleHtmlParser.js +78 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
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.rules โ toggles for built-in rules
|
|
61
|
+
options.customRules โ array 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
|
+
|
package/out/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Exposes the core lint function and types
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.lint = void 0;
|
|
5
|
+
var linter_1 = require("./linter");
|
|
6
|
+
Object.defineProperty(exports, "lint", { enumerable: true, get: function () { return linter_1.lint; } });
|
package/out/linter.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
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 builtInRules = {
|
|
27
|
+
requireSectionHeading: requireSectionHeading_1.default,
|
|
28
|
+
enforceHeadingOrder: enforceHeadingOrder_1.default,
|
|
29
|
+
singleH1: singleH1_1.default,
|
|
30
|
+
requireAltText: requireAltText_1.default,
|
|
31
|
+
requireLabelForFormControls: requireLabelForFormControls_1.default,
|
|
32
|
+
enforceListNesting: enforceListNesting_1.default,
|
|
33
|
+
requireLinkText: requireLinkText_1.default,
|
|
34
|
+
requireTableCaption: requireTableCaption_1.default,
|
|
35
|
+
preventEmptyInlineTags: preventEmptyInlineTags_1.default,
|
|
36
|
+
requireHrefOnAnchors: requireHrefOnAnchors_1.default,
|
|
37
|
+
requireButtonText: requireButtonText_1.default,
|
|
38
|
+
requireIframeTitle: requireIframeTitle_1.default,
|
|
39
|
+
requireHtmlLang: requireHtmlLang_1.default,
|
|
40
|
+
requireImageInputAlt: requireImageInputAlt_1.default,
|
|
41
|
+
requireNavLinks: requireNavLinks_1.default,
|
|
42
|
+
uniqueIds: uniqueIds_1.default,
|
|
43
|
+
};
|
|
44
|
+
const defaultOptions = {
|
|
45
|
+
rules: {
|
|
46
|
+
requireSectionHeading: true,
|
|
47
|
+
enforceHeadingOrder: true,
|
|
48
|
+
singleH1: true,
|
|
49
|
+
requireAltText: true,
|
|
50
|
+
requireLabelForFormControls: true,
|
|
51
|
+
enforceListNesting: true,
|
|
52
|
+
requireLinkText: true,
|
|
53
|
+
requireTableCaption: true,
|
|
54
|
+
preventEmptyInlineTags: true,
|
|
55
|
+
requireHrefOnAnchors: true,
|
|
56
|
+
requireButtonText: true,
|
|
57
|
+
requireIframeTitle: true,
|
|
58
|
+
requireHtmlLang: true,
|
|
59
|
+
requireImageInputAlt: true,
|
|
60
|
+
requireNavLinks: true,
|
|
61
|
+
uniqueIds: true,
|
|
62
|
+
},
|
|
63
|
+
customRules: [],
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Lint HTML/JSX/TSX content.
|
|
67
|
+
*/
|
|
68
|
+
function lint(content, options = defaultOptions) {
|
|
69
|
+
var _a;
|
|
70
|
+
const opts = {
|
|
71
|
+
rules: { ...defaultOptions.rules, ...(options.rules || {}) },
|
|
72
|
+
customRules: (_a = options.customRules) !== null && _a !== void 0 ? _a : defaultOptions.customRules,
|
|
73
|
+
};
|
|
74
|
+
const results = [];
|
|
75
|
+
const activeRules = [];
|
|
76
|
+
for (const name in opts.rules) {
|
|
77
|
+
const enabled = opts.rules[name];
|
|
78
|
+
if (enabled && builtInRules[name]) {
|
|
79
|
+
activeRules.push(builtInRules[name]());
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (opts.customRules)
|
|
83
|
+
activeRules.push(...opts.customRules);
|
|
84
|
+
activeRules.forEach(r => r.init && r.init());
|
|
85
|
+
let ast = null;
|
|
86
|
+
try {
|
|
87
|
+
ast = (0, parser_1.parse)(content, {
|
|
88
|
+
sourceType: 'module',
|
|
89
|
+
plugins: ['typescript', 'jsx'],
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
ast = null;
|
|
94
|
+
}
|
|
95
|
+
if (ast) {
|
|
96
|
+
(0, traverse_1.default)(ast, {
|
|
97
|
+
JSXElement: {
|
|
98
|
+
enter(path) {
|
|
99
|
+
for (const rule of activeRules) {
|
|
100
|
+
if (rule.enterJsx) {
|
|
101
|
+
results.push(...rule.enterJsx(path));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
exit(path) {
|
|
106
|
+
for (const rule of activeRules) {
|
|
107
|
+
if (rule.exitJsx) {
|
|
108
|
+
results.push(...rule.exitJsx(path));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
activeRules.forEach(r => r.end && results.push(...r.end()));
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
const root = (0, simpleHtmlParser_1.parse)(content);
|
|
118
|
+
const walk = (node) => {
|
|
119
|
+
for (const rule of activeRules) {
|
|
120
|
+
if (rule.enterHtml) {
|
|
121
|
+
results.push(...rule.enterHtml(node));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (node.children) {
|
|
125
|
+
for (const child of node.children) {
|
|
126
|
+
walk(child);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const rule of activeRules) {
|
|
130
|
+
if (rule.exitHtml) {
|
|
131
|
+
results.push(...rule.exitHtml(node));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
walk(root);
|
|
136
|
+
activeRules.forEach(r => r.end && results.push(...r.end()));
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = enforceHeadingOrder;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
function enforceHeadingOrder() {
|
|
6
|
+
let last = 0;
|
|
7
|
+
return {
|
|
8
|
+
name: 'enforceHeadingOrder',
|
|
9
|
+
init() { last = 0; },
|
|
10
|
+
enterHtml(node) {
|
|
11
|
+
if (node.type === 'element' && /^h[1-6]$/.test(node.tagName)) {
|
|
12
|
+
const lvl = parseInt(node.tagName.charAt(1), 10);
|
|
13
|
+
if (last && lvl > last + 1) {
|
|
14
|
+
const message = `Heading level skipped: <${node.tagName}> after <h${last}>`;
|
|
15
|
+
last = lvl;
|
|
16
|
+
return [{ line: 0, column: 0, message, rule: 'enforceHeadingOrder' }];
|
|
17
|
+
}
|
|
18
|
+
last = lvl;
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
},
|
|
22
|
+
enterJsx(path) {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
const tag = (0, utils_1.getTag)(path);
|
|
25
|
+
if (/^h[1-6]$/.test(tag)) {
|
|
26
|
+
const lvl = parseInt(tag.charAt(1), 10);
|
|
27
|
+
if (last && lvl > last + 1) {
|
|
28
|
+
const message = `Heading level skipped: <${tag}> after <h${last}>`;
|
|
29
|
+
last = lvl;
|
|
30
|
+
const line = ((_b = (_a = path.node.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 1) - 1;
|
|
31
|
+
const column = (_d = (_c = path.node.loc) === null || _c === void 0 ? void 0 : _c.start.column) !== null && _d !== void 0 ? _d : 0;
|
|
32
|
+
return [{ line, column, message, rule: 'enforceHeadingOrder' }];
|
|
33
|
+
}
|
|
34
|
+
last = lvl;
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = enforceListNesting;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
const utils_1 = require("./utils");
|
|
39
|
+
function enforceListNesting() {
|
|
40
|
+
const stack = [];
|
|
41
|
+
return {
|
|
42
|
+
name: 'enforceListNesting',
|
|
43
|
+
enterHtml(node) {
|
|
44
|
+
if (node.type === 'element') {
|
|
45
|
+
stack.push(node.tagName);
|
|
46
|
+
if (node.tagName === 'li') {
|
|
47
|
+
const parent = stack[stack.length - 2];
|
|
48
|
+
if (!parent || !['ul', 'ol'].includes(parent)) {
|
|
49
|
+
return [{ line: 0, column: 0, message: '<li> must be inside a <ul> or <ol>', rule: 'enforceListNesting' }];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
},
|
|
55
|
+
exitHtml(node) {
|
|
56
|
+
if (node.type === 'element') {
|
|
57
|
+
stack.pop();
|
|
58
|
+
}
|
|
59
|
+
return [];
|
|
60
|
+
},
|
|
61
|
+
enterJsx(path) {
|
|
62
|
+
var _a, _b, _c, _d, _e, _f;
|
|
63
|
+
const tag = (0, utils_1.getTag)(path);
|
|
64
|
+
if (tag === 'li') {
|
|
65
|
+
const parent = (_b = (_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.parentPath) === null || _b === void 0 ? void 0 : _b.node;
|
|
66
|
+
if (parent) {
|
|
67
|
+
const pTag = t.isJSXIdentifier(parent.openingElement.name) ? parent.openingElement.name.name.toLowerCase() : '';
|
|
68
|
+
if (!['ul', 'ol'].includes(pTag)) {
|
|
69
|
+
const line = ((_d = (_c = path.node.loc) === null || _c === void 0 ? void 0 : _c.start.line) !== null && _d !== void 0 ? _d : 1) - 1;
|
|
70
|
+
const column = (_f = (_e = path.node.loc) === null || _e === void 0 ? void 0 : _e.start.column) !== null && _f !== void 0 ? _f : 0;
|
|
71
|
+
return [{ line, column, message: '<li> must be inside a <ul> or <ol>', rule: 'enforceListNesting' }];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = preventEmptyInlineTags;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
const utils_1 = require("./utils");
|
|
39
|
+
const inlineTags = new Set(['strong', 'em', 'b', 'i', 'u', 'small', 'mark', 'del', 'ins']);
|
|
40
|
+
function preventEmptyInlineTags() {
|
|
41
|
+
const stack = [];
|
|
42
|
+
return {
|
|
43
|
+
name: 'preventEmptyInlineTags',
|
|
44
|
+
enterHtml(node) {
|
|
45
|
+
if (node.type === 'element' && inlineTags.has(node.tagName)) {
|
|
46
|
+
stack.push({ tag: node.tagName, found: false });
|
|
47
|
+
}
|
|
48
|
+
else if (node.type === 'text') {
|
|
49
|
+
if (stack.length && node.text.trim())
|
|
50
|
+
stack[stack.length - 1].found = true;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
},
|
|
54
|
+
exitHtml(node) {
|
|
55
|
+
if (node.type === 'element' && inlineTags.has(node.tagName)) {
|
|
56
|
+
const e = stack.pop();
|
|
57
|
+
if (e && !e.found)
|
|
58
|
+
return [{ line: 0, column: 0, message: `<${e.tag}> tag should not be empty`, rule: 'preventEmptyInlineTags' }];
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
},
|
|
62
|
+
enterJsx(path) {
|
|
63
|
+
const tag = (0, utils_1.getTag)(path);
|
|
64
|
+
if (inlineTags.has(tag))
|
|
65
|
+
stack.push({ tag, found: false });
|
|
66
|
+
return [];
|
|
67
|
+
},
|
|
68
|
+
exitJsx(path) {
|
|
69
|
+
var _a, _b, _c, _d, _e;
|
|
70
|
+
const tag = (0, utils_1.getTag)(path);
|
|
71
|
+
if (inlineTags.has(tag)) {
|
|
72
|
+
const e = stack.pop();
|
|
73
|
+
const hasText = ((_a = path.parentPath) === null || _a === void 0 ? void 0 : _a.node).children.some(c => (t.isJSXText(c) && c.value.trim()) || t.isJSXExpressionContainer(c));
|
|
74
|
+
if (e && !(e.found || hasText)) {
|
|
75
|
+
const line = ((_c = (_b = path.node.loc) === null || _b === void 0 ? void 0 : _b.start.line) !== null && _c !== void 0 ? _c : 1) - 1;
|
|
76
|
+
const column = (_e = (_d = path.node.loc) === null || _d === void 0 ? void 0 : _d.start.column) !== null && _e !== void 0 ? _e : 0;
|
|
77
|
+
return [{ line, column, message: `<${tag}> tag should not be empty`, rule: 'preventEmptyInlineTags' }];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = requireAltText;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
function requireAltText() {
|
|
39
|
+
return {
|
|
40
|
+
name: 'requireAltText',
|
|
41
|
+
enterHtml(node) {
|
|
42
|
+
if (node.type === 'element' &&
|
|
43
|
+
node.tagName === 'img' &&
|
|
44
|
+
(!('alt' in node.attrs) || !node.attrs.alt.trim())) {
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
line: 0, // line/column handling omitted for brevity
|
|
48
|
+
column: 0,
|
|
49
|
+
message: '<img> tag missing alt attribute',
|
|
50
|
+
rule: 'requireAltText',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
},
|
|
56
|
+
enterJsx(path) {
|
|
57
|
+
const opening = path.node.openingElement;
|
|
58
|
+
const name = t.isJSXIdentifier(opening.name) ? opening.name.name : '';
|
|
59
|
+
if (name !== 'img')
|
|
60
|
+
return [];
|
|
61
|
+
const altAttr = opening.attributes.find((a) => t.isJSXAttribute(a) &&
|
|
62
|
+
t.isJSXIdentifier(a.name) &&
|
|
63
|
+
a.name.name === 'alt');
|
|
64
|
+
if (!altAttr || !t.isStringLiteral(altAttr.value) || altAttr.value.value.trim() === '') {
|
|
65
|
+
const loc = opening.loc.start;
|
|
66
|
+
return [
|
|
67
|
+
{
|
|
68
|
+
line: loc.line - 1,
|
|
69
|
+
column: loc.column,
|
|
70
|
+
message: '<img> tag missing alt attribute',
|
|
71
|
+
rule: 'requireAltText',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = requireAltTextJSX;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
function requireAltTextJSX(path) {
|
|
39
|
+
var _a, _b, _c, _d;
|
|
40
|
+
const opening = path.node.openingElement;
|
|
41
|
+
const name = t.isJSXIdentifier(opening.name) ? opening.name.name : '';
|
|
42
|
+
if (name !== 'img') {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const altAttr = opening.attributes.find((a) => t.isJSXAttribute(a) &&
|
|
46
|
+
t.isJSXIdentifier(a.name) &&
|
|
47
|
+
a.name.name === 'alt');
|
|
48
|
+
if (!altAttr ||
|
|
49
|
+
!t.isStringLiteral(altAttr.value) ||
|
|
50
|
+
altAttr.value.value.trim() === '') {
|
|
51
|
+
const line = ((_b = (_a = opening.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 1) - 1;
|
|
52
|
+
const column = (_d = (_c = opening.loc) === null || _c === void 0 ? void 0 : _c.start.column) !== null && _d !== void 0 ? _d : 0;
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
line,
|
|
56
|
+
column,
|
|
57
|
+
message: '<img> tag missing alt attribute',
|
|
58
|
+
rule: 'requireAltText',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = requireButtonText;
|
|
37
|
+
const t = __importStar(require("@babel/types"));
|
|
38
|
+
const utils_1 = require("./utils");
|
|
39
|
+
function requireButtonText() {
|
|
40
|
+
const stack = [];
|
|
41
|
+
return {
|
|
42
|
+
name: 'requireButtonText',
|
|
43
|
+
enterHtml(node) {
|
|
44
|
+
if (node.type === 'element' && node.tagName === 'button') {
|
|
45
|
+
const aria = node.attrs['aria-label'];
|
|
46
|
+
stack.push({ found: !!(aria && aria.trim()), hadEmptyAria: aria !== undefined && !String(aria).trim() });
|
|
47
|
+
}
|
|
48
|
+
else if (node.type === 'text') {
|
|
49
|
+
if (stack.length && node.text.trim())
|
|
50
|
+
stack[stack.length - 1].found = true;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
},
|
|
54
|
+
exitHtml(node) {
|
|
55
|
+
if (node.type === 'element' && node.tagName === 'button') {
|
|
56
|
+
const info = stack.pop();
|
|
57
|
+
if (info && !info.found) {
|
|
58
|
+
return [{ line: 0, column: 0, message: '<button> missing accessible text', rule: 'requireButtonText' }];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
},
|
|
63
|
+
enterJsx(path) {
|
|
64
|
+
var _a, _b, _c, _d;
|
|
65
|
+
const opening = path.node.openingElement;
|
|
66
|
+
const tag = t.isJSXIdentifier(opening.name) ? opening.name.name.toLowerCase() : '';
|
|
67
|
+
if (tag === 'button') {
|
|
68
|
+
const aria = (0, utils_1.getJsxAttr)(opening, 'aria-label');
|
|
69
|
+
if (!aria || !aria.trim()) {
|
|
70
|
+
const line = ((_b = (_a = opening.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 1) - 1;
|
|
71
|
+
const column = (_d = (_c = opening.loc) === null || _c === void 0 ? void 0 : _c.start.column) !== null && _d !== void 0 ? _d : 0;
|
|
72
|
+
return [{ line, column, message: '<button> missing accessible text', rule: 'requireButtonText' }];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|