Skip to content

Commit bf3f39b

Browse files
authored
feat: add checkMissingCells option to table-column-count (#434)
* feat: add checkMissingCells option to table-column-count * change "will" to "might" in missingCells message * refactor types * rename message id and tweak docs * update examples
1 parent 466f80e commit bf3f39b

File tree

3 files changed

+239
-17
lines changed

3 files changed

+239
-17
lines changed

docs/rules/table-column-count.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ Examples of **correct** code for this rule:
5858
| Single Cell |
5959
```
6060

61+
## Options
62+
63+
The following options are available on this rule:
64+
65+
* `checkMissingCells: boolean` - When set to `true`, the rule will also flag rows that have fewer cells than the header row. (default: `false`)
66+
67+
Examples of **incorrect** code when configured as `"table-column-count": ["error", { checkMissingCells: true }]`:
68+
69+
```markdown
70+
<!-- eslint markdown/table-column-count: ["error", { checkMissingCells: true }] -->
71+
72+
<!-- Data row with fewer cells than header -->
73+
| Col A | Col B | Col C |
74+
| ----- | ----- | ----- |
75+
| 1 | | 3 |
76+
| 4 | 5 |
77+
```
78+
6179
## When Not To Use It
6280

6381
If you intentionally create Markdown tables where data rows are expected to contain more cells than the header, and you have a specific (perhaps non-standard) processing or rendering pipeline that handles this scenario correctly, you might choose to disable this rule. However, adhering to this rule is recommended for typical GFM rendering and data consistency.

src/rules/table-column-count.js

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99

1010
/**
1111
* @import { MarkdownRuleDefinition } from "../types.js";
12-
* @typedef {"inconsistentColumnCount"} TableColumnCountMessageIds
13-
* @typedef {[]} TableColumnCountOptions
12+
* @typedef {"extraCells" | "missingCells"} TableColumnCountMessageIds
13+
* @typedef {[{ checkMissingCells?: boolean }]} TableColumnCountOptions
1414
* @typedef {MarkdownRuleDefinition<{ RuleOptions: TableColumnCountOptions, MessageIds: TableColumnCountMessageIds }>} TableColumnCountRuleDefinition
1515
*/
1616

@@ -31,12 +31,30 @@ export default {
3131
},
3232

3333
messages: {
34-
inconsistentColumnCount:
34+
extraCells:
3535
"Table column count mismatch (Expected: {{expectedCells}}, Actual: {{actualCells}}), extra data starting here will be ignored.",
36+
missingCells:
37+
"Table column count mismatch (Expected: {{expectedCells}}, Actual: {{actualCells}}), row might be missing data.",
3638
},
39+
40+
schema: [
41+
{
42+
type: "object",
43+
properties: {
44+
checkMissingCells: {
45+
type: "boolean",
46+
},
47+
},
48+
additionalProperties: false,
49+
},
50+
],
51+
52+
defaultOptions: [{ checkMissingCells: false }],
3753
},
3854

3955
create(context) {
56+
const [{ checkMissingCells }] = context.options;
57+
4058
return {
4159
table(node) {
4260
if (node.children.length < 1) {
@@ -49,20 +67,39 @@ export default {
4967
for (let i = 1; i < node.children.length; i++) {
5068
const currentRow = node.children[i];
5169
const actualCellsLength = currentRow.children.length;
70+
const lastActualCellNode =
71+
currentRow.children[actualCellsLength - 1];
5272

5373
if (actualCellsLength > expectedCellsLength) {
5474
const firstExtraCellNode =
5575
currentRow.children[expectedCellsLength];
5676

57-
const lastActualCellNode =
58-
currentRow.children[actualCellsLength - 1];
59-
6077
context.report({
6178
loc: {
6279
start: firstExtraCellNode.position.start,
6380
end: lastActualCellNode.position.end,
6481
},
65-
messageId: "inconsistentColumnCount",
82+
messageId: "extraCells",
83+
data: {
84+
actualCells: String(actualCellsLength),
85+
expectedCells: String(expectedCellsLength),
86+
},
87+
});
88+
} else if (
89+
checkMissingCells &&
90+
actualCellsLength < expectedCellsLength
91+
) {
92+
context.report({
93+
loc: {
94+
start: {
95+
column:
96+
lastActualCellNode.position.end.column -
97+
1,
98+
line: lastActualCellNode.position.end.line,
99+
},
100+
end: currentRow.position.end,
101+
},
102+
messageId: "missingCells",
66103
data: {
67104
actualCells: String(actualCellsLength),
68105
expectedCells: String(expectedCellsLength),

tests/rules/table-column-count.test.js

Lines changed: 177 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,24 @@ ruleTester.run("table-column-count", rule, {
8888
| abc | def |
8989
| --- | --- |
9090
`,
91+
{
92+
code: dedent`
93+
| Header | Header |
94+
| ------ | ------ |
95+
| Cell | Cell |
96+
| Cell | Cell |
97+
`,
98+
options: [{ checkMissingCells: true }],
99+
},
100+
{
101+
code: dedent`
102+
| Header | Header |
103+
| ------ | ------ |
104+
| Cell | |
105+
| Cell | Cell |
106+
`,
107+
options: [{ checkMissingCells: true }],
108+
},
91109
],
92110

93111
invalid: [
@@ -99,7 +117,7 @@ ruleTester.run("table-column-count", rule, {
99117
`,
100118
errors: [
101119
{
102-
messageId: "inconsistentColumnCount",
120+
messageId: "extraCells",
103121
data: { actualCells: "3", expectedCells: "2" },
104122
line: 3,
105123
column: 17,
@@ -116,7 +134,7 @@ ruleTester.run("table-column-count", rule, {
116134
`,
117135
errors: [
118136
{
119-
messageId: "inconsistentColumnCount",
137+
messageId: "extraCells",
120138
data: { actualCells: "4", expectedCells: "2" },
121139
line: 3,
122140
column: 17,
@@ -133,7 +151,7 @@ ruleTester.run("table-column-count", rule, {
133151
`,
134152
errors: [
135153
{
136-
messageId: "inconsistentColumnCount",
154+
messageId: "extraCells",
137155
data: { actualCells: "2", expectedCells: "1" },
138156
line: 3,
139157
column: 5,
@@ -155,7 +173,7 @@ ruleTester.run("table-column-count", rule, {
155173
`,
156174
errors: [
157175
{
158-
messageId: "inconsistentColumnCount",
176+
messageId: "extraCells",
159177
data: { actualCells: "3", expectedCells: "2" },
160178
line: 5,
161179
column: 21,
@@ -173,7 +191,7 @@ ruleTester.run("table-column-count", rule, {
173191
`,
174192
errors: [
175193
{
176-
messageId: "inconsistentColumnCount",
194+
messageId: "extraCells",
177195
data: { actualCells: "3", expectedCells: "2" },
178196
line: 4,
179197
column: 11,
@@ -191,7 +209,7 @@ ruleTester.run("table-column-count", rule, {
191209
`,
192210
errors: [
193211
{
194-
messageId: "inconsistentColumnCount",
212+
messageId: "extraCells",
195213
data: { actualCells: "3", expectedCells: "2" },
196214
line: 3,
197215
column: 13,
@@ -209,15 +227,15 @@ ruleTester.run("table-column-count", rule, {
209227
`,
210228
errors: [
211229
{
212-
messageId: "inconsistentColumnCount",
230+
messageId: "extraCells",
213231
data: { actualCells: "3", expectedCells: "2" },
214232
line: 3,
215233
column: 13,
216234
endLine: 3,
217235
endColumn: 23,
218236
},
219237
{
220-
messageId: "inconsistentColumnCount",
238+
messageId: "extraCells",
221239
data: { actualCells: "3", expectedCells: "2" },
222240
line: 4,
223241
column: 13,
@@ -236,15 +254,15 @@ ruleTester.run("table-column-count", rule, {
236254
`,
237255
errors: [
238256
{
239-
messageId: "inconsistentColumnCount",
257+
messageId: "extraCells",
240258
data: { actualCells: "3", expectedCells: "2" },
241259
line: 3,
242260
column: 13,
243261
endLine: 3,
244262
endColumn: 23,
245263
},
246264
{
247-
messageId: "inconsistentColumnCount",
265+
messageId: "extraCells",
248266
data: { actualCells: "3", expectedCells: "2" },
249267
line: 5,
250268
column: 13,
@@ -253,5 +271,154 @@ ruleTester.run("table-column-count", rule, {
253271
},
254272
],
255273
},
274+
{
275+
code: dedent`
276+
| Header | Header | Header |
277+
| ------ | ------ | ------ |
278+
| Cell | Cell |
279+
`,
280+
options: [{ checkMissingCells: true }],
281+
errors: [
282+
{
283+
messageId: "missingCells",
284+
data: { actualCells: "2", expectedCells: "3" },
285+
line: 3,
286+
column: 19,
287+
endLine: 3,
288+
endColumn: 20,
289+
},
290+
],
291+
},
292+
{
293+
code: dedent`
294+
| Col A | Col B | Col C |
295+
| ----- | ----- | ----- |
296+
| Cell | | Cell |
297+
| Cell | Cell |
298+
`,
299+
options: [{ checkMissingCells: true }],
300+
errors: [
301+
{
302+
messageId: "missingCells",
303+
data: { actualCells: "2", expectedCells: "3" },
304+
line: 4,
305+
column: 17,
306+
endLine: 4,
307+
endColumn: 18,
308+
},
309+
],
310+
},
311+
{
312+
code: dedent`
313+
| Col A | Col B | Col C |
314+
| ----- | ----- | ----- |
315+
| Cell |
316+
| Cell | Cell |
317+
| Cell | Cell | Cell |
318+
`,
319+
options: [{ checkMissingCells: true }],
320+
errors: [
321+
{
322+
messageId: "missingCells",
323+
data: { actualCells: "1", expectedCells: "3" },
324+
line: 3,
325+
column: 9,
326+
endLine: 3,
327+
endColumn: 10,
328+
},
329+
{
330+
messageId: "missingCells",
331+
data: { actualCells: "2", expectedCells: "3" },
332+
line: 4,
333+
column: 17,
334+
endLine: 4,
335+
endColumn: 18,
336+
},
337+
],
338+
},
339+
{
340+
code: dedent`
341+
| Table |
342+
| ----- |
343+
| Cell | Cell |
344+
| Cell |
345+
| Cell | Cell |
346+
`,
347+
options: [{ checkMissingCells: true }],
348+
errors: [
349+
{
350+
messageId: "extraCells",
351+
data: { actualCells: "2", expectedCells: "1" },
352+
line: 3,
353+
column: 9,
354+
endLine: 3,
355+
endColumn: 18,
356+
},
357+
{
358+
messageId: "extraCells",
359+
data: { actualCells: "2", expectedCells: "1" },
360+
line: 5,
361+
column: 9,
362+
endLine: 5,
363+
endColumn: 18,
364+
},
365+
],
366+
},
367+
{
368+
code: dedent`
369+
| Table | Header |
370+
| ----- | ------ |
371+
| Cell | Cell | Cell |
372+
| Cell |
373+
| Cell | Cell |
374+
`,
375+
options: [{ checkMissingCells: true }],
376+
errors: [
377+
{
378+
messageId: "extraCells",
379+
data: { actualCells: "3", expectedCells: "2" },
380+
line: 3,
381+
column: 18,
382+
endLine: 3,
383+
endColumn: 28,
384+
},
385+
{
386+
messageId: "missingCells",
387+
data: { actualCells: "1", expectedCells: "2" },
388+
line: 4,
389+
column: 9,
390+
endLine: 4,
391+
endColumn: 10,
392+
},
393+
],
394+
},
395+
{
396+
code: dedent`
397+
| Table | Header | Header |
398+
| ----- | ------ | ------ |
399+
| Cell | Cell | Cell |
400+
| Cell | Cell | Cell | Cell |
401+
| Cell |
402+
`,
403+
options: [{ checkMissingCells: true }],
404+
errors: [
405+
{
406+
messageId: "extraCells",
407+
data: { actualCells: "4", expectedCells: "3" },
408+
line: 4,
409+
column: 27,
410+
endLine: 4,
411+
endColumn: 37,
412+
},
413+
{
414+
messageId: "missingCells",
415+
data: { actualCells: "1", expectedCells: "3" },
416+
line: 5,
417+
column: 9,
418+
endLine: 5,
419+
endColumn: 10,
420+
},
421+
],
422+
},
256423
],
257424
});

0 commit comments

Comments
 (0)