Skip to content

Commit 31a9bf1

Browse files
committed
Add support for header-only tables (without data rows)
1 parent 9ff8442 commit 31a9bf1

9 files changed

Lines changed: 124 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ All notable changes will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased]
8-
N/A
8+
### Added
9+
- Issue #86: Add support for header-only tables (without data rows)
910

1011
## 3.7.0 - 2025-08-29
1112
### Added

src/modelFactory/tableValidator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class TableValidator {
99
return false;
1010
const rawRows: string[][] = this._selectionInterpreter.allRows(selection);
1111

12-
const sizeValid = rawRows.length > 2 && // at least two rows are required (besides the separator)
12+
const sizeValid = rawRows.length >= 2 && // at least header and separator rows are required
1313
rawRows[0].length > 1 && // at least two columns are required
1414
rawRows.every(r => r.length == rawRows[0].length); // all rows of a column must match the length of the first row of that column
1515

src/tableFinding/tableFinder.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,17 @@ export class TableFinder {
4242

4343
private getNextValidTableRange(document: Document, separatorRowIndex: number): { range: Range, ignoreBlockStarted: boolean} {
4444
let firstTableFileRow = separatorRowIndex - 1;
45-
let lastTableFileRow = separatorRowIndex + 1;
45+
let lastTableFileRow = separatorRowIndex;
4646
let selection = null;
4747
let ignoreBlockedStarted = false;
4848

49+
// accept also tables with no body (just header + separator rows), if valid, as a fallback in case more table rows cannot be found
50+
const headerSeparatorSelection = document.getText(new Range(firstTableFileRow, lastTableFileRow));
51+
if (this._tableValidator.isValid(headerSeparatorSelection)) {
52+
selection = headerSeparatorSelection;
53+
}
54+
55+
lastTableFileRow++;
4956
while (lastTableFileRow < document.lines.length) {
5057
// when the ignore-start is in the middle of a possible table don't go further
5158
if (document.lines[lastTableFileRow].value.trim() == this._ignoreStart) {

src/writers/tableStringWriter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ export class TableStringWriter {
1111
if (table == null) throw new Error("Table can't be null.");
1212
if (table.header == null) throw new Error("Table must have a header.");
1313
if (table.separator == null) throw new Error("Table must have a separator.");
14-
if (table.rows == null || table.rowCount == 0) throw new Error("Table must have rows.");
14+
if (table.rows == null) throw new Error("Table rows can't be null.");
15+
if (table.columnCount == 0) throw new Error("Table must have at least one column.");
1516

1617
let buffer = "";
1718
buffer += this.writeRowViewModel(table.header, table, true);
18-
buffer += this.writeRowViewModel(table.separator, table, true);
19+
buffer += this.writeRowViewModel(table.separator, table, table.rowCount > 0);
1920
buffer += this.writeRows(table);
2021

2122
return buffer;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Test header-only tables
2+
3+
Simple header-only table without borders:
4+
Column1 | Column2 | Column3
5+
--------|---------|--------
6+
7+
Header-only table with borders:
8+
| Column A | Column B | Column C |
9+
|----------|----------|----------|
10+
11+
Mixed scenario with header-only and regular tables:
12+
Name | Age
13+
-----|----
14+
15+
And a regular table:
16+
Name | Age
17+
-----|----
18+
John | 25
19+
Jane | 30
20+
21+
Header-only with alignment:
22+
Left column | Center column | Right column
23+
:-----------|:-------------:|------------:
24+
25+
| Base-10 Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | Drop-Off |
26+
|--------------:|----:|---:|---:|---:|--:|--:|--:|--:|---------:|
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Test header-only tables
2+
3+
Simple header-only table without borders:
4+
Column1|Column2|Column3
5+
-------|-------|-------
6+
7+
Header-only table with borders:
8+
| Column A | Column B | Column C |
9+
|----------|----------|----------|
10+
11+
Mixed scenario with header-only and regular tables:
12+
Name|Age
13+
----|---
14+
15+
And a regular table:
16+
Name|Age
17+
----|---
18+
John|25
19+
Jane|30
20+
21+
Header-only with alignment:
22+
Left column | Center column | Right column
23+
:----|:------:|------:
24+
25+
| Base-10 Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 | Drop-Off |
26+
| -: | -: | -: | -: | -: | -: | -: | -: | -: | -: |

test/unitTests/modelFactory/tableValidator.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ suite("TableValidator tests", () => {
5555
assert.strictEqual(isValid, true);
5656
});
5757

58+
test("isValid() with header and separator only (no data rows) returns true", () => {
59+
const sut = createSut();
60+
61+
const isValid: boolean = sut.isValid(
62+
`header1|header2
63+
-------|-------`
64+
);
65+
66+
assert.strictEqual(isValid, true);
67+
});
68+
5869
test("isValid() with separator alignment options returns true", () => {
5970
const sut = createSut();
6071

test/unitTests/tableFinding/tableFinder.test.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ suite("TableFinder tests", () => {
9999
|int|32|Integer
100100
101101
|Invalid|
102-
|-|-
102+
|-|-|
103103
104104
|Primitive Type|Size(bit)|Wrapper
105105
|-|-|-
@@ -111,6 +111,27 @@ suite("TableFinder tests", () => {
111111
assert.deepStrictEqual(range, new Range(9, 12));
112112
});
113113

114+
test("getNextRange() finds header only table", () => {
115+
const sut = createSut();
116+
const document = new Document(`
117+
|Primitive Type|Size(bit)|Wrapper
118+
|-|-|-
119+
|short|16|Short
120+
|int|32|Integer
121+
122+
|Primitive Type|Size(bit)|Wrapper
123+
|-|-|-
124+
125+
|Primitive Type|Size(bit)|Wrapper
126+
|-|-|-
127+
|short|16|Short
128+
|int|32|Integer`);
129+
130+
let range = sut.getNextRange(document, 5);
131+
132+
assert.deepStrictEqual(range, new Range(6, 7));
133+
});
134+
114135
test("getNextRange() for table with alignments the expected range is returned", () => {
115136
const sut = createSut();
116137
const document = new Document(`no table on first line
@@ -157,8 +178,8 @@ suite("TableFinder tests", () => {
157178
test("getNextRange() table after an end ignore is found", () => {
158179
const sut = createSut();
159180
const document = new Document(`no table on first line
160-
|Primitive Type|Size(bit)|Wrapper
161-
|-|-|-
181+
|Primitive Type|Size(bit)|Wrapper|
182+
|-|-|
162183
<!-- markdown-table-prettify-ignore-start -->
163184
|short|16|Short
164185
|int|32|Integer
@@ -167,7 +188,7 @@ suite("TableFinder tests", () => {
167188
|int|32|Integer
168189
169190
|Invalid|
170-
|-|-
191+
|-|-|
171192
172193
|Primitive Type|Size(bit)|Wrapper
173194
|-|-|-
@@ -184,7 +205,7 @@ suite("TableFinder tests", () => {
184205
const document = new Document(`no table on first line
185206
186207
|Invalid|
187-
|-|-
208+
|-|-|
188209
189210
<!-- markdown-table-prettify-ignore-end -->
190211
|Primitive Type|Size(bit)|Wrapper

test/unitTests/writers/tableStringWriter.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,26 +105,43 @@ suite("TableStringWriter tests", () => {
105105
assert.throws(() => writer.writeTable(input));
106106
});
107107

108-
test("writeTable() table with null rows throws exception", () => {
108+
test("writeTable() table with empty header and separator columns throws exception", () => {
109109
const input : TableViewModel = new TableViewModel();
110110
input.header = makeRowViewModel([]);
111111
input.separator = makeRowViewModel([]);
112+
input.rows = [];
112113

113114
const writer = createSut();
114115

115116
assert.throws(() => writer.writeTable(input));
116117
});
117118

118-
test("writeTable() table with no rows throws exception", () => {
119+
test("writeTable() table with null rows array throws exception", () => {
119120
const input : TableViewModel = new TableViewModel();
120-
input.header = makeRowViewModel([]);
121-
input.rows = [ ];
121+
input.header = makeRowViewModel(["c1", "c2"]);
122+
input.separator = makeRowViewModel(["--", "--"]);
123+
input.rows = null;
122124

123125
const writer = createSut();
124126

125127
assert.throws(() => writer.writeTable(input));
126128
});
127129

130+
test("writeTable() table with no rows is allowed", () => {
131+
const input : TableViewModel = new TableViewModel();
132+
input.header = makeRowViewModel(["c1", "c2"]);
133+
input.separator = makeRowViewModel(["--", "--"]);
134+
input.rows = [ ];
135+
136+
const tableText: string = createSut().writeTable(input);
137+
138+
assertExt.isNotNull(tableText);
139+
const lines = tableText.split(/\r\n|\r|\n/);
140+
assert.strictEqual(lines.length, 2);
141+
assert.strictEqual(lines[0], "c1|c2");
142+
assert.strictEqual(lines[1], "--|--");
143+
});
144+
128145
test("writeTable() writes left borders on all rows for viewModel having hasLeftBorderSet", () => {
129146
const input : TableViewModel = new TableViewModel();
130147
input.hasLeftBorder = true;

0 commit comments

Comments
 (0)