@rgaa-checker/eslint-plugin-rgaa 0.1.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 ADDED
@@ -0,0 +1,65 @@
1
+ # @rgaa-checker/eslint-plugin-rgaa
2
+
3
+ Règles ESLint d'accessibilité **mappées au RGAA 4.1.2**, en couche au-dessus de
4
+ [`eslint-plugin-jsx-a11y`](https://github.com/jsx-eslint/eslint-plugin-jsx-a11y).
5
+ Chaque règle indique le critère RGAA visé et renvoie vers sa fiche.
6
+
7
+ > **Lint partiel, pas une preuve de conformité.** Un linter statique ne couvre
8
+ > qu'une fraction du RGAA. D'après la méthodologie d'axe-core, **~60 % des
9
+ > critères WCAG restent à vérifier manuellement**. Ce plugin aide à corriger tôt
10
+ > les erreurs détectables dans le code source ; pour le reste, lancez un
11
+ > [scan complet RGAA Checker](https://rgaa-checker.com?utm_source=eslint).
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -D @rgaa-checker/eslint-plugin-rgaa eslint-plugin-jsx-a11y eslint
17
+ ```
18
+
19
+ ## Usage (ESLint 9, flat config)
20
+
21
+ Le plugin suppose un **parseur compatible JSX déjà configuré** dans votre projet
22
+ (Next.js, `typescript-eslint`, etc.). Ajoutez la config recommandée à la suite
23
+ de votre configuration existante :
24
+
25
+ ```js
26
+ // eslint.config.js
27
+ import rgaa from '@rgaa-checker/eslint-plugin-rgaa';
28
+
29
+ export default [
30
+ // ...votre config qui établit le parseur JSX/TS (Next.js, typescript-eslint)...
31
+ ...rgaa.configs.recommended,
32
+ ];
33
+ ```
34
+
35
+ `rgaa.configs.recommended` active les checks de `jsx-a11y` **et** les règles RGAA
36
+ ci-dessous.
37
+
38
+ Pour n'activer que les règles RGAA (sans `jsx-a11y`) :
39
+
40
+ ```js
41
+ import rgaa from '@rgaa-checker/eslint-plugin-rgaa';
42
+
43
+ export default [
44
+ {
45
+ plugins: { rgaa },
46
+ rules: {
47
+ 'rgaa/img-alt': 'error',
48
+ 'rgaa/no-noninteractive-onclick': 'error',
49
+ },
50
+ },
51
+ ];
52
+ ```
53
+
54
+ ## Règles
55
+
56
+ | Règle | Critère RGAA | Description |
57
+ |-------|--------------|-------------|
58
+ | `rgaa/img-alt` | 1.1 | Les images doivent avoir une alternative textuelle (`alt`). |
59
+ | `rgaa/no-noninteractive-onclick` | 7.1 | Une action au clic doit être sur un `<button>` (ou `<a>` pour naviguer), pas un élément statique. |
60
+
61
+ D'autres critères seront ajoutés progressivement.
62
+
63
+ ## Licence
64
+
65
+ MPL-2.0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Canonical RGAA fiche URL for a criterion, with acquisition attribution.
3
+ * The fiche route is /guide/<criterion> (cf. criteria-catalog primaryGuide).
4
+ */
5
+ export declare const FICHE_BASE = "https://rgaa-checker.com/guide";
6
+ export declare function ficheUrl(criterion: string): string;
7
+ //# sourceMappingURL=criteria.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criteria.d.ts","sourceRoot":"","sources":["../src/criteria.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,UAAU,mCAAmC,CAAC;AAE3D,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Canonical RGAA fiche URL for a criterion, with acquisition attribution.
3
+ * The fiche route is /guide/<criterion> (cf. criteria-catalog primaryGuide).
4
+ */
5
+ export const FICHE_BASE = 'https://rgaa-checker.com/guide';
6
+ export function ficheUrl(criterion) {
7
+ return `${FICHE_BASE}/${criterion}?utm_source=eslint`;
8
+ }
9
+ //# sourceMappingURL=criteria.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"criteria.js","sourceRoot":"","sources":["../src/criteria.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,gCAAgC,CAAC;AAE3D,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACtC,OAAO,GAAG,UAAU,IAAI,SAAS,oBAAoB,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,13 @@
1
+ declare const plugin: {
2
+ meta: {
3
+ name: string;
4
+ version: string;
5
+ };
6
+ rules: {
7
+ 'img-alt': import("eslint").Rule.RuleModule;
8
+ 'no-noninteractive-onclick': import("eslint").Rule.RuleModule;
9
+ };
10
+ configs: Record<string, unknown>;
11
+ };
12
+ export default plugin;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM;;;;;;;;;aAMO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CACzC,CAAC;AAgBF,eAAe,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ import jsxA11y from 'eslint-plugin-jsx-a11y';
2
+ import imgAlt from './rules/img-alt.js';
3
+ import noNoninteractiveOnclick from './rules/no-noninteractive-onclick.js';
4
+ const plugin = {
5
+ meta: { name: '@rgaa-checker/eslint-plugin-rgaa', version: '0.1.0' },
6
+ rules: {
7
+ 'img-alt': imgAlt,
8
+ 'no-noninteractive-onclick': noNoninteractiveOnclick,
9
+ },
10
+ configs: {},
11
+ };
12
+ // Flat config (ESLint 9): compose jsx-a11y's recommended config with our RGAA
13
+ // rules. Element 1 brings the jsx-a11y checks; element 2 registers the `rgaa`
14
+ // plugin and enables our rules.
15
+ plugin.configs.recommended = [
16
+ jsxA11y.flatConfigs.recommended,
17
+ {
18
+ plugins: { rgaa: plugin },
19
+ rules: {
20
+ 'rgaa/img-alt': 'error',
21
+ 'rgaa/no-noninteractive-onclick': 'error',
22
+ },
23
+ },
24
+ ];
25
+ export default plugin;
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,wBAAwB,CAAC;AAC7C,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,uBAAuB,MAAM,sCAAsC,CAAC;AAE3E,MAAM,MAAM,GAAG;IACX,IAAI,EAAE,EAAE,IAAI,EAAE,kCAAkC,EAAE,OAAO,EAAE,OAAO,EAAE;IACpE,KAAK,EAAE;QACH,SAAS,EAAE,MAAM;QACjB,2BAA2B,EAAE,uBAAuB;KACvD;IACD,OAAO,EAAE,EAA6B;CACzC,CAAC;AAEF,8EAA8E;AAC9E,8EAA8E;AAC9E,gCAAgC;AAChC,MAAM,CAAC,OAAO,CAAC,WAAW,GAAG;IACzB,OAAO,CAAC,WAAW,CAAC,WAAW;IAC/B;QACI,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;QACzB,KAAK,EAAE;YACH,cAAc,EAAE,OAAO;YACvB,gCAAgC,EAAE,OAAO;SAC5C;KACJ;CACJ,CAAC;AAEF,eAAe,MAAM,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal shape of the JSX AST nodes our rules inspect.
3
+ *
4
+ * ESLint's estree types do not include JSX nodes, so rule visitors would
5
+ * otherwise fall back to `any`. This local interface gives us just enough
6
+ * type-safety for the attributes/name fields we read, without `any`.
7
+ */
8
+ export interface JsxOpeningElement {
9
+ name?: {
10
+ type?: string;
11
+ name?: string;
12
+ };
13
+ attributes: Array<{
14
+ type: string;
15
+ name?: {
16
+ name?: string;
17
+ };
18
+ }>;
19
+ }
20
+ //# sourceMappingURL=jsx-ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-ast.d.ts","sourceRoot":"","sources":["../src/jsx-ast.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CACjE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jsx-ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-ast.js","sourceRoot":"","sources":["../src/jsx-ast.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=img-alt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"img-alt.d.ts","sourceRoot":"","sources":["../../src/rules/img-alt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAMnC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAuChB,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { ficheUrl } from '../criteria.js';
2
+ const CRITERION = '1.1';
3
+ const rule = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Les images doivent avoir une alternative textuelle (attribut alt). RGAA 1.1.',
8
+ url: ficheUrl(CRITERION),
9
+ },
10
+ schema: [],
11
+ messages: {
12
+ missingAlt: "Image sans alternative textuelle : ajoutez un attribut alt (alt=\"\" si l'image est décorative). (RGAA 1.1, lint partiel — voir la fiche)",
13
+ },
14
+ },
15
+ create(context) {
16
+ return {
17
+ JSXOpeningElement(node) {
18
+ if (node.name?.type !== 'JSXIdentifier' || node.name.name !== 'img') {
19
+ return;
20
+ }
21
+ let hasAlt = false;
22
+ let hasSpread = false;
23
+ for (const attr of node.attributes) {
24
+ if (attr.type === 'JSXSpreadAttribute') {
25
+ hasSpread = true;
26
+ continue;
27
+ }
28
+ if (attr.type === 'JSXAttribute' && attr.name?.name === 'alt') {
29
+ hasAlt = true;
30
+ }
31
+ }
32
+ // A spread (e.g. {...props}) may carry alt — don't flag, avoid false positives.
33
+ if (hasSpread)
34
+ return;
35
+ if (!hasAlt) {
36
+ context.report({ node: node, messageId: 'missingAlt' });
37
+ }
38
+ },
39
+ };
40
+ },
41
+ };
42
+ export default rule;
43
+ //# sourceMappingURL=img-alt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"img-alt.js","sourceRoot":"","sources":["../../src/rules/img-alt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB,MAAM,IAAI,GAAoB;IAC1B,IAAI,EAAE;QACF,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACF,WAAW,EACP,8EAA8E;YAClF,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC;SAC3B;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACN,UAAU,EACN,2IAA2I;SAClJ;KACJ;IACD,MAAM,CAAC,OAAO;QACV,OAAO;YACH,iBAAiB,CAAC,IAAuB;gBACrC,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,eAAe,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;oBAClE,OAAO;gBACX,CAAC;gBACD,IAAI,MAAM,GAAG,KAAK,CAAC;gBACnB,IAAI,SAAS,GAAG,KAAK,CAAC;gBACtB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACjC,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;wBACrC,SAAS,GAAG,IAAI,CAAC;wBACjB,SAAS;oBACb,CAAC;oBACD,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,KAAK,EAAE,CAAC;wBAC5D,MAAM,GAAG,IAAI,CAAC;oBAClB,CAAC;gBACL,CAAC;gBACD,gFAAgF;gBAChF,IAAI,SAAS;oBAAE,OAAO;gBACtB,IAAI,CAAC,MAAM,EAAE,CAAC;oBACV,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAA4B,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC;gBACpF,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Rule } from 'eslint';
2
+ declare const rule: Rule.RuleModule;
3
+ export default rule;
4
+ //# sourceMappingURL=no-noninteractive-onclick.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-noninteractive-onclick.d.ts","sourceRoot":"","sources":["../../src/rules/no-noninteractive-onclick.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAiBnC,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UAsChB,CAAC;AAEF,eAAe,IAAI,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { ficheUrl } from '../criteria.js';
2
+ const CRITERION = '7.1';
3
+ // Native HTML elements that are already interactive — onClick is legitimate.
4
+ const INTERACTIVE = new Set([
5
+ 'button',
6
+ 'a',
7
+ 'input',
8
+ 'select',
9
+ 'textarea',
10
+ 'option',
11
+ 'summary',
12
+ ]);
13
+ const rule = {
14
+ meta: {
15
+ type: 'problem',
16
+ docs: {
17
+ description: "Une action au clic doit être portée par un élément interactif (button pour agir, a pour naviguer), pas un élément statique. RGAA 7.1.",
18
+ url: ficheUrl(CRITERION),
19
+ },
20
+ schema: [],
21
+ messages: {
22
+ noninteractive: 'Action au clic sur un élément non interactif : utilisez <button> pour une action (ou <a> pour naviguer). (RGAA 7.1, lint partiel — voir la fiche)',
23
+ },
24
+ },
25
+ create(context) {
26
+ return {
27
+ JSXOpeningElement(node) {
28
+ const name = node.name;
29
+ if (name?.type !== 'JSXIdentifier')
30
+ return;
31
+ const tag = name.name;
32
+ // Skip missing names, components (Capitalized) and natively-interactive tags.
33
+ if (!tag || !/^[a-z]/.test(tag) || INTERACTIVE.has(tag))
34
+ return;
35
+ let hasOnClick = false;
36
+ let hasRole = false;
37
+ for (const attr of node.attributes) {
38
+ if (attr.type !== 'JSXAttribute')
39
+ continue;
40
+ const an = attr.name?.name;
41
+ if (an === 'onClick')
42
+ hasOnClick = true;
43
+ if (an === 'role')
44
+ hasRole = true;
45
+ }
46
+ // A role (e.g. role="button") signals an intentional ARIA widget — don't flag here.
47
+ if (hasOnClick && !hasRole) {
48
+ context.report({ node: node, messageId: 'noninteractive' });
49
+ }
50
+ },
51
+ };
52
+ },
53
+ };
54
+ export default rule;
55
+ //# sourceMappingURL=no-noninteractive-onclick.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-noninteractive-onclick.js","sourceRoot":"","sources":["../../src/rules/no-noninteractive-onclick.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,MAAM,SAAS,GAAG,KAAK,CAAC;AAExB,6EAA6E;AAC7E,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IACxB,QAAQ;IACR,GAAG;IACH,OAAO;IACP,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,SAAS;CACZ,CAAC,CAAC;AAEH,MAAM,IAAI,GAAoB;IAC1B,IAAI,EAAE;QACF,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACF,WAAW,EACP,uIAAuI;YAC3I,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC;SAC3B;QACD,MAAM,EAAE,EAAE;QACV,QAAQ,EAAE;YACN,cAAc,EACV,mJAAmJ;SAC1J;KACJ;IACD,MAAM,CAAC,OAAO;QACV,OAAO;YACH,iBAAiB,CAAC,IAAuB;gBACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACvB,IAAI,IAAI,EAAE,IAAI,KAAK,eAAe;oBAAE,OAAO;gBAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC;gBACtB,8EAA8E;gBAC9E,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,OAAO;gBAEhE,IAAI,UAAU,GAAG,KAAK,CAAC;gBACvB,IAAI,OAAO,GAAG,KAAK,CAAC;gBACpB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;oBACjC,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc;wBAAE,SAAS;oBAC3C,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;oBAC3B,IAAI,EAAE,KAAK,SAAS;wBAAE,UAAU,GAAG,IAAI,CAAC;oBACxC,IAAI,EAAE,KAAK,MAAM;wBAAE,OAAO,GAAG,IAAI,CAAC;gBACtC,CAAC;gBACD,oFAAoF;gBACpF,IAAI,UAAU,IAAI,CAAC,OAAO,EAAE,CAAC;oBACzB,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAA4B,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACxF,CAAC;YACL,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC;AAEF,eAAe,IAAI,CAAC"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@rgaa-checker/eslint-plugin-rgaa",
3
+ "version": "0.1.0",
4
+ "description": "Règles ESLint d'accessibilité mappées au RGAA 4.1.2, par-dessus eslint-plugin-jsx-a11y.",
5
+ "license": "MPL-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": ["dist", "README.md"],
10
+ "keywords": ["eslint", "eslintplugin", "eslint-plugin", "accessibility", "a11y", "rgaa", "jsx-a11y"],
11
+ "homepage": "https://rgaa-checker.com",
12
+ "repository": { "type": "git", "url": "https://github.com/rgaa-checker/devtools.git" },
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "test": "vitest run"
16
+ },
17
+ "peerDependencies": {
18
+ "eslint": ">=9",
19
+ "eslint-plugin-jsx-a11y": ">=6.10"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "eslint": "^9.39.4",
24
+ "eslint-plugin-jsx-a11y": "^6.10.2",
25
+ "typescript": "^5.0.0",
26
+ "vitest": "^2.1.9"
27
+ },
28
+ "engines": { "node": ">=18.0.0" }
29
+ }