@markuplint/rules 4.7.0 → 4.8.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/CHANGELOG.md +17 -0
- package/lib/index.d.ts +13 -11
- package/lib/index.js +4 -0
- package/lib/no-ambiguous-navigable-target-names/index.d.ts +2 -0
- package/lib/no-ambiguous-navigable-target-names/index.js +44 -0
- package/lib/no-ambiguous-navigable-target-names/meta.d.ts +4 -0
- package/lib/no-ambiguous-navigable-target-names/meta.js +3 -0
- package/lib/table-row-column-alignment/find-children.d.ts +2 -0
- package/lib/table-row-column-alignment/find-children.js +13 -0
- package/lib/table-row-column-alignment/grid.d.ts +22 -0
- package/lib/table-row-column-alignment/grid.js +186 -0
- package/lib/table-row-column-alignment/index.d.ts +2 -0
- package/lib/table-row-column-alignment/index.js +73 -0
- package/lib/table-row-column-alignment/meta.d.ts +4 -0
- package/lib/table-row-column-alignment/meta.js +3 -0
- package/lib/table-row-column-alignment/types.d.ts +2 -0
- package/lib/table-row-column-alignment/types.js +1 -0
- package/package.json +8 -8
- package/schema.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
# [4.8.0](https://github.com/markuplint/markuplint/compare/@markuplint/rules@4.8.0-alpha.0...@markuplint/rules@4.8.0) (2024-05-04)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @markuplint/rules
|
|
9
|
+
|
|
10
|
+
# [4.8.0-alpha.0](https://github.com/markuplint/markuplint/compare/@markuplint/rules@4.7.0...@markuplint/rules@4.8.0-alpha.0) (2024-05-04)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- **rules:** add `no-ambiguous-navigable-target-names` rule ([742db4e](https://github.com/markuplint/markuplint/commit/742db4eb98b8f27e8a1f6a82d3b6541871e02a5c))
|
|
15
|
+
- **rules:** add `table-row-column-alignment` rule ([4b071f8](https://github.com/markuplint/markuplint/commit/4b071f8d7dae0f1500e1a77046b289489eb5a598))
|
|
16
|
+
- **rules:** apply `no-ambiguous-navigable-target-names` to build-in rules ([93d34f0](https://github.com/markuplint/markuplint/commit/93d34f0ead2624107a5b6f315af0c8bbd4f1e1ec))
|
|
17
|
+
- **rules:** apply `table-row-column-alignment` to build-in rules ([85de609](https://github.com/markuplint/markuplint/commit/85de6098813cd7c3167099f9e7e6250ca8324539))
|
package/lib/index.d.ts
CHANGED
|
@@ -16,13 +16,7 @@ declare const rules: {
|
|
|
16
16
|
readonly 'id-duplication': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
17
17
|
readonly 'ineffective-attr': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
18
18
|
readonly 'invalid-attr': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, {
|
|
19
|
-
allowAttrs?:
|
|
20
|
-
enum: [string, ...string[]];
|
|
21
|
-
} | {
|
|
22
|
-
pattern: string;
|
|
23
|
-
} | {
|
|
24
|
-
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
25
|
-
}> | (string | {
|
|
19
|
+
allowAttrs?: (string | {
|
|
26
20
|
name: string;
|
|
27
21
|
value: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType | ({
|
|
28
22
|
enum: [string, ...string[]];
|
|
@@ -31,14 +25,14 @@ declare const rules: {
|
|
|
31
25
|
} | {
|
|
32
26
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
33
27
|
});
|
|
34
|
-
})[] |
|
|
35
|
-
disallowAttrs?: Record<string, {
|
|
28
|
+
})[] | Record<string, {
|
|
36
29
|
enum: [string, ...string[]];
|
|
37
30
|
} | {
|
|
38
31
|
pattern: string;
|
|
39
32
|
} | {
|
|
40
33
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
41
|
-
}> |
|
|
34
|
+
}> | undefined;
|
|
35
|
+
disallowAttrs?: (string | {
|
|
42
36
|
name: string;
|
|
43
37
|
value: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType | ({
|
|
44
38
|
enum: [string, ...string[]];
|
|
@@ -47,7 +41,13 @@ declare const rules: {
|
|
|
47
41
|
} | {
|
|
48
42
|
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
49
43
|
});
|
|
50
|
-
})[] |
|
|
44
|
+
})[] | Record<string, {
|
|
45
|
+
enum: [string, ...string[]];
|
|
46
|
+
} | {
|
|
47
|
+
pattern: string;
|
|
48
|
+
} | {
|
|
49
|
+
type: import("packages/@markuplint/ml-spec/lib/index.js").AttributeType;
|
|
50
|
+
}> | undefined;
|
|
51
51
|
ignoreAttrNamePrefix?: string | string[] | undefined;
|
|
52
52
|
allowToAddPropertiesForPretender?: boolean | undefined;
|
|
53
53
|
attrs?: Record<string, ({
|
|
@@ -66,6 +66,7 @@ declare const rules: {
|
|
|
66
66
|
labelEachArea: boolean;
|
|
67
67
|
}>>;
|
|
68
68
|
readonly 'neighbor-popovers': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
69
|
+
readonly 'no-ambiguous-navigable-target-names': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
|
|
69
70
|
readonly 'no-boolean-attr-value': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
70
71
|
readonly 'no-consecutive-br': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
71
72
|
readonly 'no-default-value': Readonly<import("@markuplint/ml-core").RuleSeed<import("@markuplint/ml-core").RuleConfigValue, undefined>>;
|
|
@@ -99,6 +100,7 @@ declare const rules: {
|
|
|
99
100
|
ignoreHasMutableContents: boolean;
|
|
100
101
|
}>>;
|
|
101
102
|
readonly 'required-h1': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, import("./required-h1/index.js").Options>>;
|
|
103
|
+
readonly 'table-row-column-alignment': Readonly<import("@markuplint/ml-core").RuleSeed<boolean, undefined>>;
|
|
102
104
|
readonly 'use-list': Readonly<import("@markuplint/ml-core").RuleSeed<readonly string[], {
|
|
103
105
|
spaceNeededBullets?: string[] | undefined;
|
|
104
106
|
noPrev?: boolean | undefined;
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import InvalidAttr from './invalid-attr/index.js';
|
|
|
16
16
|
import LabelHasControl from './label-has-control/index.js';
|
|
17
17
|
import LandmarkRoles from './landmark-roles/index.js';
|
|
18
18
|
import NeighborPopovers from './neighbor-popovers/index.js';
|
|
19
|
+
import NoAmbiguousNavigableTargetNames from './no-ambiguous-navigable-target-names/index.js';
|
|
19
20
|
import NoBooleanAttrValue from './no-boolean-attr-value/index.js';
|
|
20
21
|
import NoConsecutiveBr from './no-consecutive-br/index.js';
|
|
21
22
|
import NoDefaultValue from './no-default-value/index.js';
|
|
@@ -32,6 +33,7 @@ import RequireDatetime from './require-datetime/index.js';
|
|
|
32
33
|
import RequiredAttr from './required-attr/index.js';
|
|
33
34
|
import RequiredElement from './required-element/index.js';
|
|
34
35
|
import RequiredH1 from './required-h1/index.js';
|
|
36
|
+
import TableRowColumnAlignment from './table-row-column-alignment/index.js';
|
|
35
37
|
import UseList from './use-list/index.js';
|
|
36
38
|
import WaiAria from './wai-aria/index.js';
|
|
37
39
|
const rules = {
|
|
@@ -53,6 +55,7 @@ const rules = {
|
|
|
53
55
|
'label-has-control': LabelHasControl,
|
|
54
56
|
'landmark-roles': LandmarkRoles,
|
|
55
57
|
'neighbor-popovers': NeighborPopovers,
|
|
58
|
+
'no-ambiguous-navigable-target-names': NoAmbiguousNavigableTargetNames,
|
|
56
59
|
'no-boolean-attr-value': NoBooleanAttrValue,
|
|
57
60
|
'no-consecutive-br': NoConsecutiveBr,
|
|
58
61
|
'no-default-value': NoDefaultValue,
|
|
@@ -69,6 +72,7 @@ const rules = {
|
|
|
69
72
|
'required-attr': RequiredAttr,
|
|
70
73
|
'required-element': RequiredElement,
|
|
71
74
|
'required-h1': RequiredH1,
|
|
75
|
+
'table-row-column-alignment': TableRowColumnAlignment,
|
|
72
76
|
'use-list': UseList,
|
|
73
77
|
'wai-aria': WaiAria,
|
|
74
78
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createRule, getAttrSpecs } from '@markuplint/ml-core';
|
|
2
|
+
import { valueCheck } from '../attr-check.js';
|
|
3
|
+
import meta from './meta.js';
|
|
4
|
+
export default createRule({
|
|
5
|
+
meta: meta,
|
|
6
|
+
defaultSeverity: 'warning',
|
|
7
|
+
async verify({ document, report, t }) {
|
|
8
|
+
await document.walkOn('Attr', attr => {
|
|
9
|
+
if (!attr.valueNode) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const attrSpecs = getAttrSpecs(attr.ownerElement, document.specs);
|
|
13
|
+
if (!attrSpecs) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const attrSpec = attrSpecs.find(spec => spec.name === attr.name);
|
|
17
|
+
if (attrSpec?.type !== 'NavigableTargetNameOrKeyword' && attrSpec?.type !== 'NavigableTargetName') {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const value = attr.value.trim();
|
|
21
|
+
if (value.startsWith('_')) {
|
|
22
|
+
// This is a keyword
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const valueWithUnderscore = `_${value}`;
|
|
26
|
+
const invalid = valueCheck(t, attr.name, valueWithUnderscore, 'NavigableTargetNameOrKeyword');
|
|
27
|
+
if (invalid !== false) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
report({
|
|
31
|
+
scope: attr,
|
|
32
|
+
line: attr.valueNode.startLine,
|
|
33
|
+
col: attr.valueNode.startCol,
|
|
34
|
+
raw: attr.valueNode.raw,
|
|
35
|
+
message:
|
|
36
|
+
//
|
|
37
|
+
t("don't use {0}", t('a {0}', t('ambiguous {0}', 'navigable target name'))) +
|
|
38
|
+
(attrSpec.type === 'NavigableTargetNameOrKeyword'
|
|
39
|
+
? t('. ') + t('Did you mean "{0}"?', valueWithUnderscore)
|
|
40
|
+
: ''),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const findCache = {};
|
|
2
|
+
export function findChildren(
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
4
|
+
el, selector) {
|
|
5
|
+
const key = `${el.uuid}:${selector}`;
|
|
6
|
+
const cached = findCache[key];
|
|
7
|
+
if (cached) {
|
|
8
|
+
return cached;
|
|
9
|
+
}
|
|
10
|
+
const children = [...el.children].filter(child => child.matches(selector));
|
|
11
|
+
findCache[key] = children;
|
|
12
|
+
return children;
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CellType } from './types.js';
|
|
2
|
+
import type { Element } from '@markuplint/ml-core';
|
|
3
|
+
export declare class Grid {
|
|
4
|
+
#private;
|
|
5
|
+
readonly tbodyGrid: ReadonlyArray<ReadonlyArray<CellType>>;
|
|
6
|
+
readonly tfootGrid: ReadonlyArray<ReadonlyArray<CellType>>;
|
|
7
|
+
readonly theadGrid: ReadonlyArray<ReadonlyArray<CellType>>;
|
|
8
|
+
constructor(table: Element<boolean>);
|
|
9
|
+
getAllRowElements(): Element<boolean, undefined>[];
|
|
10
|
+
getAllRows(): (readonly CellType[])[];
|
|
11
|
+
getBaseColLength(): number;
|
|
12
|
+
getSections(): {
|
|
13
|
+
section: readonly (readonly CellType[])[];
|
|
14
|
+
elements: readonly Element<boolean, undefined>[];
|
|
15
|
+
}[];
|
|
16
|
+
hasOverlapped(): boolean;
|
|
17
|
+
log(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare function getOverflowRowSpan(rows: ReadonlyArray<ReadonlyArray<CellType>>, rowElements: ReadonlyArray<Element<boolean>>): {
|
|
20
|
+
rowSpan: import("@markuplint/ml-core").Attr<boolean, undefined>;
|
|
21
|
+
} | null;
|
|
22
|
+
export declare function getIndexes(row: readonly CellType[]): (number | null)[];
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _Grid_tbodyRowElements, _Grid_tfootRowElements, _Grid_theadRowElements;
|
|
13
|
+
import { findChildren } from './find-children.js';
|
|
14
|
+
export class Grid {
|
|
15
|
+
constructor(
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
17
|
+
table) {
|
|
18
|
+
_Grid_tbodyRowElements.set(this, void 0);
|
|
19
|
+
_Grid_tfootRowElements.set(this, void 0);
|
|
20
|
+
_Grid_theadRowElements.set(this, void 0);
|
|
21
|
+
const thead = findChildren(table, 'thead')[0];
|
|
22
|
+
const tbody = findChildren(table, 'tbody')[0];
|
|
23
|
+
const tfoot = findChildren(table, 'tfoot')[0];
|
|
24
|
+
__classPrivateFieldSet(this, _Grid_theadRowElements, thead ? findChildren(thead, 'tr') : [], "f");
|
|
25
|
+
__classPrivateFieldSet(this, _Grid_tbodyRowElements, tbody ? findChildren(tbody, 'tr') : [], "f");
|
|
26
|
+
__classPrivateFieldSet(this, _Grid_tfootRowElements, tfoot ? findChildren(tfoot, 'tr') : [], "f");
|
|
27
|
+
this.theadGrid = createGrid(__classPrivateFieldGet(this, _Grid_theadRowElements, "f"));
|
|
28
|
+
this.tbodyGrid = createGrid(__classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"));
|
|
29
|
+
this.tfootGrid = createGrid(__classPrivateFieldGet(this, _Grid_tfootRowElements, "f"));
|
|
30
|
+
}
|
|
31
|
+
getAllRowElements() {
|
|
32
|
+
return [...__classPrivateFieldGet(this, _Grid_theadRowElements, "f"), ...__classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"), ...__classPrivateFieldGet(this, _Grid_tfootRowElements, "f")];
|
|
33
|
+
}
|
|
34
|
+
getAllRows() {
|
|
35
|
+
return [
|
|
36
|
+
...this.theadGrid.filter(hasElementFilter),
|
|
37
|
+
...this.tbodyGrid.filter(hasElementFilter),
|
|
38
|
+
...this.tfootGrid.filter(hasElementFilter),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
getBaseColLength() {
|
|
42
|
+
if (this.theadGrid.length > 0) {
|
|
43
|
+
return getBaseColLength(this.theadGrid);
|
|
44
|
+
}
|
|
45
|
+
if (this.tfootGrid.length > 0) {
|
|
46
|
+
return getBaseColLength(this.tfootGrid);
|
|
47
|
+
}
|
|
48
|
+
return getBaseColLength(this.tbodyGrid);
|
|
49
|
+
}
|
|
50
|
+
getSections() {
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
section: this.theadGrid,
|
|
54
|
+
elements: __classPrivateFieldGet(this, _Grid_theadRowElements, "f"),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
section: this.tbodyGrid,
|
|
58
|
+
elements: __classPrivateFieldGet(this, _Grid_tbodyRowElements, "f"),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
section: this.tfootGrid,
|
|
62
|
+
elements: __classPrivateFieldGet(this, _Grid_tfootRowElements, "f"),
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
hasOverlapped() {
|
|
67
|
+
return (this.tbodyGrid.some(row => row.includes('x')) ||
|
|
68
|
+
this.tfootGrid.some(row => row.includes('x')) ||
|
|
69
|
+
this.theadGrid.some(row => row.includes('x')));
|
|
70
|
+
}
|
|
71
|
+
log() {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.table(this.theadGrid);
|
|
74
|
+
// eslint-disable-next-line no-console
|
|
75
|
+
console.table(this.tbodyGrid);
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.table(this.tfootGrid);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_Grid_tbodyRowElements = new WeakMap(), _Grid_tfootRowElements = new WeakMap(), _Grid_theadRowElements = new WeakMap();
|
|
81
|
+
export function getOverflowRowSpan(rows,
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
83
|
+
rowElements) {
|
|
84
|
+
for (const [rowNum] of rows.entries()) {
|
|
85
|
+
const rowEl = rowElements[rowNum];
|
|
86
|
+
if (!rowEl) {
|
|
87
|
+
const prevRow = rows[rowNum - 1];
|
|
88
|
+
const prevRowEl = rowElements[rowNum - 1];
|
|
89
|
+
if (!prevRow || !prevRowEl) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const spanStart = prevRow.indexOf('◎');
|
|
93
|
+
if (spanStart === -1) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const indexes = getIndexes(prevRow);
|
|
97
|
+
const index = indexes[spanStart];
|
|
98
|
+
if (index == null) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const cells = findChildren(prevRowEl, 'th, td');
|
|
102
|
+
const spanStartCell = cells[index];
|
|
103
|
+
if (!spanStartCell) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const rowSpan = spanStartCell.getAttributeNode('rowspan');
|
|
107
|
+
if (!rowSpan) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
rowSpan,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
export function getIndexes(row) {
|
|
118
|
+
let indexCounter = 0;
|
|
119
|
+
return row.map(col => (col === '●' || col === '◎' ? indexCounter++ : null));
|
|
120
|
+
}
|
|
121
|
+
function getBaseColLength(grid) {
|
|
122
|
+
let baseColLength;
|
|
123
|
+
if (grid.length >= 3) {
|
|
124
|
+
const totalCols = grid.reduce((acc, row) => {
|
|
125
|
+
return acc + row.length;
|
|
126
|
+
}, 0);
|
|
127
|
+
const average = Math.round(totalCols / grid.length);
|
|
128
|
+
baseColLength = grid
|
|
129
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
130
|
+
.reduce((closest, row) => {
|
|
131
|
+
return Math.abs(row.length - average) < Math.abs(closest - average) ? row.length : closest;
|
|
132
|
+
}, grid[0]?.length ?? 0);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
baseColLength = grid[0]?.length ?? 0;
|
|
136
|
+
}
|
|
137
|
+
return baseColLength;
|
|
138
|
+
}
|
|
139
|
+
function createGrid(
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
141
|
+
rows) {
|
|
142
|
+
const rowSpans = new Set();
|
|
143
|
+
const matrix = [];
|
|
144
|
+
for (const [rowNum, row] of rows.entries()) {
|
|
145
|
+
const cols = findChildren(row, 'th, td');
|
|
146
|
+
let colNum = 0;
|
|
147
|
+
matrix[rowNum] = matrix[rowNum] ?? [];
|
|
148
|
+
for (const col of cols) {
|
|
149
|
+
const colSpan = Number.parseInt(col.getAttribute('colspan') ?? '1');
|
|
150
|
+
const rowSpan = Number.parseInt(col.getAttribute('rowspan') ?? '1');
|
|
151
|
+
for (let i = 0; i < colSpan; i++) {
|
|
152
|
+
const key = `${rowNum}:${colNum}`;
|
|
153
|
+
let spanStart = false;
|
|
154
|
+
if (colSpan > 1 && i === 0) {
|
|
155
|
+
spanStart = true;
|
|
156
|
+
}
|
|
157
|
+
if (rowSpan > 1) {
|
|
158
|
+
if (i === 0) {
|
|
159
|
+
spanStart = true;
|
|
160
|
+
}
|
|
161
|
+
for (let i = 1; i <= rowSpan - 1; i++) {
|
|
162
|
+
matrix[rowNum + i] = matrix[rowNum + i] ?? [];
|
|
163
|
+
matrix[rowNum + i][colNum] = '↓';
|
|
164
|
+
rowSpans.add(`${rowNum + i}:${colNum}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const addCol = rowSpans.has(key) ? 1 : 0;
|
|
168
|
+
if (matrix[rowNum]?.[colNum] === '↓' && !spanStart && colSpan > 1) {
|
|
169
|
+
// Overlap
|
|
170
|
+
matrix[rowNum][colNum] = 'x';
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
matrix[rowNum][colNum] = spanStart ? '◎' : addCol > 0 ? '↓' : colSpan > 1 ? '→' : '●';
|
|
174
|
+
}
|
|
175
|
+
if (addCol === 1) {
|
|
176
|
+
matrix[rowNum].push('●');
|
|
177
|
+
}
|
|
178
|
+
colNum += 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return matrix;
|
|
183
|
+
}
|
|
184
|
+
function hasElementFilter(row) {
|
|
185
|
+
return row.some(cell => cell === '●' || cell === '◎');
|
|
186
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createRule } from '@markuplint/ml-core';
|
|
2
|
+
import { findChildren } from './find-children.js';
|
|
3
|
+
import { Grid, getIndexes, getOverflowRowSpan } from './grid.js';
|
|
4
|
+
import meta from './meta.js';
|
|
5
|
+
export default createRule({
|
|
6
|
+
meta,
|
|
7
|
+
defaultSeverity: 'warning',
|
|
8
|
+
verify({ document, report, t }) {
|
|
9
|
+
const tables = document.querySelectorAll('table');
|
|
10
|
+
for (const table of tables) {
|
|
11
|
+
const grid = new Grid(table);
|
|
12
|
+
const baseColLength = grid.getBaseColLength();
|
|
13
|
+
// grid.log();
|
|
14
|
+
if (grid.hasOverlapped()) {
|
|
15
|
+
report({
|
|
16
|
+
scope: table,
|
|
17
|
+
message: t(
|
|
18
|
+
//
|
|
19
|
+
'{0} are causing {1}', t(['rowspan', 'colspan'], true), 'cell overlap'),
|
|
20
|
+
});
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
for (const { section, elements } of grid.getSections()) {
|
|
24
|
+
const overflow = getOverflowRowSpan(section, elements);
|
|
25
|
+
if (overflow) {
|
|
26
|
+
report({
|
|
27
|
+
scope: overflow.rowSpan,
|
|
28
|
+
message: t('Exceeds the number of available {0}', 'rows'),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const rows = grid.getAllRows();
|
|
33
|
+
const rowElements = grid.getAllRowElements();
|
|
34
|
+
for (const [rowNum, row] of rows.entries()) {
|
|
35
|
+
const colLength = row.length;
|
|
36
|
+
const rowEl = rowElements[rowNum];
|
|
37
|
+
if (!rowEl) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const indexes = getIndexes(row);
|
|
41
|
+
if (colLength > baseColLength) {
|
|
42
|
+
const index = indexes.slice(baseColLength)[0];
|
|
43
|
+
const cells = findChildren(rowEl, 'th, td');
|
|
44
|
+
const unexpected = typeof index === 'number' ? cells[index] : null;
|
|
45
|
+
if (!unexpected) {
|
|
46
|
+
const spanStart = cells.findLast(cell => Number.parseInt(cell.getAttribute('colspan') ?? '1') > 1);
|
|
47
|
+
const colSpan = spanStart?.getAttributeNode('colspan');
|
|
48
|
+
if (!colSpan) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
report({
|
|
52
|
+
scope: colSpan,
|
|
53
|
+
message: t('Exceeds the number of available {0}', 'columns'),
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const diff = colLength - baseColLength;
|
|
58
|
+
report({
|
|
59
|
+
scope: unexpected,
|
|
60
|
+
message: t('{0} extra {1} in {2}', t(`${diff}`), t(diff === 1 ? 'column' : 'columns'), t('a {0}', t('row'))),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (colLength < baseColLength) {
|
|
64
|
+
const diff = baseColLength - colLength;
|
|
65
|
+
report({
|
|
66
|
+
scope: rowEl,
|
|
67
|
+
message: t('{0} missing {1} in {2}', t(`${diff}`), t(diff === 1 ? 'column' : 'columns'), t('a {0}', t('row'))),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/rules",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
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>",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./lib/permitted-contents/debug.js": "./lib/permitted-contents/debug.browser.js"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@markuplint/html-spec": "4.7.
|
|
29
|
-
"@markuplint/ml-core": "4.7.
|
|
30
|
-
"@markuplint/ml-spec": "4.5.
|
|
31
|
-
"@markuplint/selector": "4.6.
|
|
32
|
-
"@markuplint/shared": "4.3.
|
|
33
|
-
"@markuplint/types": "4.4.
|
|
28
|
+
"@markuplint/html-spec": "4.7.1",
|
|
29
|
+
"@markuplint/ml-core": "4.7.1",
|
|
30
|
+
"@markuplint/ml-spec": "4.5.1",
|
|
31
|
+
"@markuplint/selector": "4.6.1",
|
|
32
|
+
"@markuplint/shared": "4.3.1",
|
|
33
|
+
"@markuplint/types": "4.4.1",
|
|
34
34
|
"@types/debug": "4.1.12",
|
|
35
35
|
"@ungap/structured-clone": "1.2.0",
|
|
36
36
|
"ansi-colors": "4.1.3",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"debug": "4.3.4",
|
|
39
39
|
"type-fest": "4.18.1"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "7a5967fce14fdf66f0d8eae9d93ae5b350648d3d"
|
|
42
42
|
}
|
package/schema.json
CHANGED
|
@@ -60,6 +60,9 @@
|
|
|
60
60
|
"neighbor-popovers": {
|
|
61
61
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/neighbor-popovers/schema.json"
|
|
62
62
|
},
|
|
63
|
+
"no-ambiguous-navigable-target-names": {
|
|
64
|
+
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/no-ambiguous-navigable-target-names/schema.json"
|
|
65
|
+
},
|
|
63
66
|
"no-boolean-attr-value": {
|
|
64
67
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/no-boolean-attr-value/schema.json"
|
|
65
68
|
},
|
|
@@ -108,6 +111,9 @@
|
|
|
108
111
|
"required-h1": {
|
|
109
112
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/required-h1/schema.json"
|
|
110
113
|
},
|
|
114
|
+
"table-row-column-alignment": {
|
|
115
|
+
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/table-row-column-alignment/schema.json"
|
|
116
|
+
},
|
|
111
117
|
"use-list": {
|
|
112
118
|
"$ref": "https://raw.githubusercontent.com/markuplint/markuplint/main/packages/%40markuplint/rules/src/use-list/schema.json"
|
|
113
119
|
},
|