tailwind-a11y 0.2.0 → 0.3.1
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 +2 -0
- package/dist/cli.js +14 -3
- package/dist/cliArgs.d.ts +8 -0
- package/dist/cliArgs.js +31 -0
- package/dist/parser/extractTouchTargets.js +27 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ npm install --save-dev tailwind-a11y
|
|
|
25
25
|
npx tailwind-a11y # scans **/*.{jsx,tsx}
|
|
26
26
|
npx tailwind-a11y "src/**/*.tsx" # custom glob
|
|
27
27
|
npx tailwind-a11y --verbose # also reports what couldn't be checked, and why
|
|
28
|
+
npx tailwind-a11y --version # print the installed version
|
|
29
|
+
npx tailwind-a11y --help # usage and all options
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { relative, sep } from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import fg from "fast-glob";
|
|
5
6
|
import { extractChecks, extractContrastSkips } from "./parser/extractClasses.js";
|
|
6
7
|
import { checkContrast, checkContrastValueSkips } from "./rules/checkContrast.js";
|
|
@@ -8,6 +9,10 @@ import { extractTouchTargetChecks, extractTouchTargetSkips } from "./parser/extr
|
|
|
8
9
|
import { checkTouchTargets } from "./rules/checkTouchTarget.js";
|
|
9
10
|
import { extractFocusIndicatorChecks } from "./parser/extractFocusIndicators.js";
|
|
10
11
|
import { checkFocusIndicators } from "./rules/checkFocusIndicator.js";
|
|
12
|
+
import { parseArgs, getHelpText } from "./cliArgs.js";
|
|
13
|
+
// ../package.json resolves correctly from both src/ (dev) and dist/ (published).
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const { version: packageVersion } = require("../package.json");
|
|
11
16
|
function formatViolation(v) {
|
|
12
17
|
switch (v.type) {
|
|
13
18
|
case "contrast": {
|
|
@@ -34,9 +39,15 @@ function groupByFile(items) {
|
|
|
34
39
|
return byFile;
|
|
35
40
|
}
|
|
36
41
|
async function main() {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const { help, version, verbose, patterns } = parseArgs(process.argv.slice(2));
|
|
43
|
+
if (help) {
|
|
44
|
+
console.log(getHelpText());
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (version) {
|
|
48
|
+
console.log(packageVersion);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
40
51
|
const globPatterns = patterns.length > 0 ? patterns : ["**/*.{jsx,tsx}"];
|
|
41
52
|
const files = await fg(globPatterns, {
|
|
42
53
|
cwd: process.cwd(),
|
package/dist/cliArgs.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Split out from cli.ts so it's importable in tests without triggering
|
|
2
|
+
// cli.ts's top-level main() call (which does real file I/O on import).
|
|
3
|
+
const HELP_TEXT = `Usage: tailwind-a11y [options] [<glob>...]
|
|
4
|
+
|
|
5
|
+
Static analysis for Tailwind CSS accessibility violations -- color contrast,
|
|
6
|
+
touch target size, and focus indicator removal.
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
-v, --verbose Also report what couldn't be checked, and why
|
|
10
|
+
-V, --version Print the version number
|
|
11
|
+
-h, --help Print this help message
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
tailwind-a11y Scan **/*.{jsx,tsx} from the current directory
|
|
15
|
+
tailwind-a11y "src/**/*.tsx" Scan a custom glob pattern
|
|
16
|
+
tailwind-a11y --verbose Also report skipped/unresolvable cases
|
|
17
|
+
`;
|
|
18
|
+
export function getHelpText() {
|
|
19
|
+
return HELP_TEXT;
|
|
20
|
+
}
|
|
21
|
+
// -v/--verbose already existed before --version was added; -V (uppercase)
|
|
22
|
+
// avoids colliding with it, matching a common CLI convention.
|
|
23
|
+
const FLAGS = new Set(["--verbose", "-v", "--version", "-V", "--help", "-h"]);
|
|
24
|
+
export function parseArgs(argv) {
|
|
25
|
+
return {
|
|
26
|
+
help: argv.includes("--help") || argv.includes("-h"),
|
|
27
|
+
version: argv.includes("--version") || argv.includes("-V"),
|
|
28
|
+
verbose: argv.includes("--verbose") || argv.includes("-v"),
|
|
29
|
+
patterns: argv.filter((a) => !FLAGS.has(a)),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -17,6 +17,31 @@ function lastSizeToken(tokens, prefix) {
|
|
|
17
17
|
}
|
|
18
18
|
return found;
|
|
19
19
|
}
|
|
20
|
+
function isMeaningfulText(node) {
|
|
21
|
+
// Pure JSX-formatting whitespace (indentation/newlines between elements)
|
|
22
|
+
// doesn't count as text.
|
|
23
|
+
return !!node && t.isJSXText(node) && node.value.trim().length > 0;
|
|
24
|
+
}
|
|
25
|
+
// WCAG 2.5.8's "Inline" exception: a target inside a sentence or block of
|
|
26
|
+
// text is exempt from the minimum size, since its size is constrained by
|
|
27
|
+
// surrounding text flow rather than a deliberate layout choice. Checked via
|
|
28
|
+
// the element's *immediate* siblings only (not "any text anywhere in the
|
|
29
|
+
// parent") — a parent-wide check would exempt every sibling in something
|
|
30
|
+
// like `<p>Choose: <button/><button/></p>` just because the first button
|
|
31
|
+
// happens to sit next to text, even though the second one doesn't. That's
|
|
32
|
+
// the same "shape, not meaning" failure mode as the bg-opacity-50/ring-0
|
|
33
|
+
// false negatives documented in CLAUDE.md, just at the sibling level instead
|
|
34
|
+
// of the token level.
|
|
35
|
+
function isInlineInText(path) {
|
|
36
|
+
const parentNode = path.parentPath?.node;
|
|
37
|
+
if (!parentNode || (!t.isJSXElement(parentNode) && !t.isJSXFragment(parentNode)))
|
|
38
|
+
return false;
|
|
39
|
+
const siblings = parentNode.children;
|
|
40
|
+
const index = siblings.indexOf(path.node);
|
|
41
|
+
if (index === -1)
|
|
42
|
+
return false;
|
|
43
|
+
return isMeaningfulText(siblings[index - 1]) || isMeaningfulText(siblings[index + 1]);
|
|
44
|
+
}
|
|
20
45
|
export function extractTouchTargetChecks(code, filePath) {
|
|
21
46
|
const ast = parseJSX(code, filePath);
|
|
22
47
|
if (!ast)
|
|
@@ -39,6 +64,8 @@ export function extractTouchTargetChecks(code, filePath) {
|
|
|
39
64
|
const heightPx = spacingScale[height.value];
|
|
40
65
|
if (widthPx === undefined || heightPx === undefined)
|
|
41
66
|
return; // arbitrary/keyword/fraction — skip
|
|
67
|
+
if (isInlineInText(path))
|
|
68
|
+
return; // WCAG 2.5.8 inline exception — exempt, not a violation
|
|
42
69
|
checks.push({
|
|
43
70
|
file: filePath,
|
|
44
71
|
line: opening.loc?.start.line ?? 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tailwind-a11y",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Static analysis CLI that catches WCAG accessibility violations — color contrast, touch target size, and focus indicator removal — in Tailwind CSS class combinations before they ship.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|