@markuplint/rules 5.0.0-alpha.0 → 5.0.0-alpha.2

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/CHANGELOG.md CHANGED
@@ -3,6 +3,24 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.0.0-alpha.2](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.1...v5.0.0-alpha.2) (2026-02-23)
7
+
8
+ ### Features
9
+
10
+ - **rules:** add inline fix callbacks to three rules ([16f9600](https://github.com/markuplint/markuplint/commit/16f9600a1f495046541c195859e218ca6dd61eca))
11
+
12
+ # [5.0.0-alpha.1](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2026-02-22)
13
+
14
+ - fix(rules)!: remove complementary from top-level landmark check ([4643651](https://github.com/markuplint/markuplint/commit/4643651d256276ae0473d4756e72277d59398e3d))
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ - The landmark-roles rule no longer checks that
19
+ complementary landmarks are top-level. This aligns with axe-core's
20
+ deprecation of landmark-complementary-is-top-level.
21
+
22
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
23
+
6
24
  # [5.0.0-alpha.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
7
25
 
8
26
  ### Bug Fixes
@@ -25,21 +25,23 @@ export default createRule({
25
25
  return;
26
26
  }
27
27
  const quote = attr.startQuote?.raw;
28
- if (quote !== quoteList[attr.rule.value]) {
28
+ const expectedQuote = quoteList[attr.rule.value];
29
+ if (quote !== expectedQuote) {
29
30
  report({
30
31
  scope: attr,
31
32
  message,
33
+ fix: fixer => {
34
+ const edits = [];
35
+ if (attr.startQuote) {
36
+ edits.push(fixer.replaceText(attr.startQuote, expectedQuote));
37
+ }
38
+ if (attr.endQuote) {
39
+ edits.push(fixer.replaceText(attr.endQuote, expectedQuote));
40
+ }
41
+ return edits;
42
+ },
32
43
  });
33
44
  }
34
45
  });
35
46
  },
36
- async fix({ document }) {
37
- await document.walkOn('Attr', attr => {
38
- const quote = quoteList[attr.rule.value];
39
- if (quote && attr.startQuote && attr.startQuote.raw !== quote) {
40
- attr.startQuote.fix(quote);
41
- attr.endQuote?.fix(quote);
42
- }
43
- });
44
- },
45
47
  });
@@ -39,44 +39,17 @@ export default createRule({
39
39
  }
40
40
  }
41
41
  if (deny.test(name)) {
42
+ const nameNode = attr.nameNode;
43
+ const fixedName = value === 'lower' ? name.toLowerCase() : name.toUpperCase();
42
44
  report({
43
45
  scope: attr,
44
- line: attr.nameNode?.startLine,
45
- col: attr.nameNode?.startCol,
46
- raw: attr.nameNode?.raw,
46
+ line: nameNode?.startLine,
47
+ col: nameNode?.startCol,
48
+ raw: nameNode?.raw,
47
49
  message,
50
+ fix: nameNode ? fixer => fixer.replaceText(nameNode, fixedName) : undefined, // eslint-disable-line @typescript-eslint/strict-boolean-expressions
48
51
  });
49
52
  }
50
53
  });
51
54
  },
52
- async fix({ document }) {
53
- await document.walkOn('Attr', attr => {
54
- const el = attr.ownerElement;
55
- if (el.isForeignElement || el.elementType !== 'html') {
56
- return;
57
- }
58
- const attrSpecs = getAttrSpecs(el, document.specs);
59
- const value = attr.rule.value;
60
- /**
61
- * Ignore when it has the potential name,
62
- * it Interprets `tabIndex` to `tabindex` in JSX for example.
63
- */
64
- if (attr.nameNode?.raw !== attr.name) {
65
- return;
66
- }
67
- const name = attr.name;
68
- if (attrSpecs) {
69
- const spec = attrSpecs.find(spec => spec.name === name);
70
- if (spec && spec.caseSensitive) {
71
- return;
72
- }
73
- }
74
- if (value === 'lower') {
75
- attr.nameNode.fix(name.toLowerCase());
76
- }
77
- else {
78
- attr.nameNode.fix(name.toUpperCase());
79
- }
80
- });
81
- },
82
55
  });
@@ -22,19 +22,24 @@ export default createRule({
22
22
  }
23
23
  const ms = el.rule.severity === 'error' ? 'must' : 'should';
24
24
  const deny = el.rule.value === 'lower' ? /[A-Z]/ : /[a-z]/;
25
+ const fixedName = el.rule.value === 'lower' ? el.rawName.toLowerCase() : el.rawName.toUpperCase();
25
26
  const message = t(`{0} ${ms} be {1}`, t('{0} of {1}', 'tag names', 'HTML elements'), `${el.rule.value}case`);
26
27
  if (deny.test(el.rawName)) {
27
28
  const loc = el.getNameLocation();
29
+ const nameOffset = loc.offset + el.tagOpenChar.length;
28
30
  report({
29
31
  scope: el,
30
32
  message,
31
33
  line: loc.line,
32
34
  col: loc.col,
33
35
  raw: el.rawName,
36
+ fix: fixer => fixer.replaceText({ startOffset: nameOffset, raw: el.rawName }, fixedName),
34
37
  });
35
38
  }
36
39
  const closeTag = el.closeTag;
37
- if (closeTag && deny.test(closeTag.raw)) {
40
+ if (closeTag && deny.test(closeTag.rawName)) {
41
+ const closeNameOffset = closeTag.startOffset + el.tagOpenChar.length + '/'.length;
42
+ const fixedCloseName = el.rule.value === 'lower' ? closeTag.rawName.toLowerCase() : closeTag.rawName.toUpperCase();
38
43
  report({
39
44
  scope: {
40
45
  rule: el.rule,
@@ -43,27 +48,9 @@ export default createRule({
43
48
  raw: closeTag.raw,
44
49
  },
45
50
  message,
51
+ fix: fixer => fixer.replaceText({ startOffset: closeNameOffset, raw: closeTag.rawName }, fixedCloseName),
46
52
  });
47
53
  }
48
54
  });
49
55
  },
50
- async fix({ document }) {
51
- if (document.tagNameCaseSensitive) {
52
- return;
53
- }
54
- await document.walkOn('Element', el => {
55
- if (el.isForeignElement || el.elementType !== 'html') {
56
- return;
57
- }
58
- const deny = el.rule.value === 'lower' ? /[A-Z]/ : /[a-z]/;
59
- if (deny.test(el.nodeName)) {
60
- if (el.rule.value === 'lower') {
61
- el.fixNodeName(el.nodeName.toLowerCase());
62
- }
63
- else {
64
- el.fixNodeName(el.nodeName.toUpperCase());
65
- }
66
- }
67
- });
68
- },
69
56
  });
package/lib/index.d.ts CHANGED
@@ -44,7 +44,7 @@ declare const rules: {
44
44
  }>>;
45
45
  readonly 'label-has-control': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
46
46
  readonly 'landmark-roles': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
47
- ignoreRoles: (("banner" | "main" | "complementary" | "contentinfo") | "form" | "navigation" | "region")[];
47
+ ignoreRoles: (("banner" | "main" | "contentinfo") | "complementary" | "form" | "navigation" | "region")[];
48
48
  labelEachArea: boolean;
49
49
  }>>;
50
50
  readonly 'link-types': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
@@ -8,13 +8,13 @@ type Options = {
8
8
  labelEachArea: boolean;
9
9
  };
10
10
  /** Landmark roles that should appear at the top level of the document. */
11
- type TopLevelRoles = 'banner' | 'main' | 'complementary' | 'contentinfo';
11
+ type TopLevelRoles = 'banner' | 'main' | 'contentinfo';
12
12
  /** All recognized ARIA landmark roles for this rule. */
13
- type Roles = TopLevelRoles | 'form' | 'navigation' | 'region';
13
+ type Roles = TopLevelRoles | 'complementary' | 'form' | 'navigation' | 'region';
14
14
  /**
15
15
  * Rule that validates proper usage of ARIA landmark roles.
16
16
  *
17
- * Checks that `banner`, `main`, `complementary`, and `contentinfo` landmarks are
17
+ * Checks that `banner`, `main`, and `contentinfo` landmarks are
18
18
  * top-level (not nested inside other landmarks), and that when duplicate landmarks
19
19
  * of the same role exist, each has a unique accessible name.
20
20
  */
@@ -11,11 +11,11 @@ const selectors = {
11
11
  region: ['[role="region"]', 'section[aria-labelledby]', 'section[aria-label]', 'section[title]'],
12
12
  };
13
13
  /** Roles that are required to be top-level landmarks per WAI-ARIA practices. */
14
- const topLevelRoles = ['banner', 'main', 'complementary', 'contentinfo'];
14
+ const topLevelRoles = ['banner', 'main', 'contentinfo'];
15
15
  /**
16
16
  * Rule that validates proper usage of ARIA landmark roles.
17
17
  *
18
- * Checks that `banner`, `main`, `complementary`, and `contentinfo` landmarks are
18
+ * Checks that `banner`, `main`, and `contentinfo` landmarks are
19
19
  * top-level (not nested inside other landmarks), and that when duplicate landmarks
20
20
  * of the same role exist, each has a unique accessible name.
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/rules",
3
- "version": "5.0.0-alpha.0",
3
+ "version": "5.0.0-alpha.2",
4
4
  "description": "Built-in rules of markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -27,12 +27,12 @@
27
27
  "./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
28
28
  },
29
29
  "dependencies": {
30
- "@markuplint/html-spec": "5.0.0-alpha.0",
31
- "@markuplint/ml-core": "5.0.0-alpha.0",
32
- "@markuplint/ml-spec": "5.0.0-alpha.0",
33
- "@markuplint/selector": "5.0.0-alpha.0",
34
- "@markuplint/shared": "5.0.0-alpha.0",
35
- "@markuplint/types": "5.0.0-alpha.0",
30
+ "@markuplint/html-spec": "5.0.0-alpha.2",
31
+ "@markuplint/ml-core": "5.0.0-alpha.2",
32
+ "@markuplint/ml-spec": "5.0.0-alpha.2",
33
+ "@markuplint/selector": "5.0.0-alpha.2",
34
+ "@markuplint/shared": "5.0.0-alpha.2",
35
+ "@markuplint/types": "5.0.0-alpha.2",
36
36
  "@mdn/browser-compat-data": "^7.3.2",
37
37
  "@types/debug": "4.1.12",
38
38
  "ansi-colors": "4.1.3",
@@ -42,5 +42,5 @@
42
42
  "image-size": "^2.0.2",
43
43
  "type-fest": "5.4.4"
44
44
  },
45
- "gitHead": "13dcfc84ec83d87360c720e253383b60767e1b56"
45
+ "gitHead": "31ccf1e81443ea3f93597d287595211f1823ddcf"
46
46
  }