2020 * content and the enclosing pipes.
2121 */
2222
23- import { spawnSync } from " node:child_process"
24- import { readFileSync , writeFileSync } from " node:fs"
25- import path from " node:path"
23+ import { spawnSync } from ' node:child_process' ;
24+ import { readFileSync , writeFileSync } from ' node:fs' ;
25+ import path from ' node:path' ;
2626
27- const ROOT = path . resolve ( import . meta. dir , ".." )
27+ const ROOT = path . resolve ( import . meta. dir , '..' ) ;
2828
29- const ok = new Set ( [ " ---" , " :---" , " ---:" , " :---:" ] )
29+ const ok = new Set ( [ ' ---' , ' :---' , ' ---:' , ' :---:' ] ) ;
3030
3131function tracked ( ) {
32- const r = spawnSync ( " git" , [ " ls-files" , " *.md" ] , { cwd : ROOT , encoding : " utf8" } )
32+ const r = spawnSync ( ' git' , [ ' ls-files' , ' *.md' ] , { cwd : ROOT , encoding : ' utf8' } ) ;
3333 if ( r . status !== 0 ) {
34- console . error ( r . stderr ?. trim ( ) || " git ls-files failed" )
35- process . exit ( 1 )
34+ console . error ( r . stderr ?. trim ( ) || ' git ls-files failed' ) ;
35+ process . exit ( 1 ) ;
3636 }
37- return r . stdout . split ( "\n" ) . filter ( Boolean )
37+ return r . stdout . split ( '\n' ) . filter ( Boolean ) ;
3838}
3939
4040function skip ( file : string ) {
41- const norm = file . replaceAll ( "\\" , "/" ) . toLowerCase ( )
42- if ( norm . startsWith ( " .changeset/" ) ) return true
43- if ( norm === " changelog.md" || norm . endsWith ( " /changelog.md" ) ) return true
44- if ( norm . includes ( " node_modules/" ) ) return true
45- return false
41+ const norm = file . replaceAll ( '\\' , '/' ) . toLowerCase ( ) ;
42+ if ( norm . startsWith ( ' .changeset/' ) ) return true ;
43+ if ( norm === ' changelog.md' || norm . endsWith ( ' /changelog.md' ) ) return true ;
44+ if ( norm . includes ( ' node_modules/' ) ) return true ;
45+ return false ;
4646}
4747
48- type Issue = { file : string ; line : number ; kind : " separator" | " content" ; detail : string }
48+ type Issue = { file : string ; line : number ; kind : ' separator' | ' content' ; detail : string } ;
4949
5050function split ( row : string ) {
5151 // Split on unescaped pipes, drop the empty leading/trailing cells that come
5252 // from rows starting and ending with a pipe.
53- const cells : string [ ] = [ ]
54- let buf = ""
53+ const cells : string [ ] = [ ] ;
54+ let buf = '' ;
5555 for ( let i = 0 ; i < row . length ; i ++ ) {
56- const c = row [ i ]
57- if ( c === "\\" && row [ i + 1 ] === "|" ) {
58- buf += " \\|"
59- i ++
60- continue
56+ const c = row [ i ] ;
57+ if ( c === '\\' && row [ i + 1 ] === '|' ) {
58+ buf += ' \\|' ;
59+ i ++ ;
60+ continue ;
6161 }
62- if ( c === "|" ) {
63- cells . push ( buf )
64- buf = ""
65- continue
62+ if ( c === '|' ) {
63+ cells . push ( buf ) ;
64+ buf = '' ;
65+ continue ;
6666 }
67- buf += c
67+ buf += c ;
6868 }
69- cells . push ( buf )
70- if ( cells . length >= 2 && cells [ 0 ] . trim ( ) === "" ) cells . shift ( )
71- if ( cells . length >= 1 && cells [ cells . length - 1 ] . trim ( ) === "" ) cells . pop ( )
72- return cells
69+ cells . push ( buf ) ;
70+ if ( cells . length >= 2 && cells [ 0 ] . trim ( ) === '' ) cells . shift ( ) ;
71+ if ( cells . length >= 1 && cells [ cells . length - 1 ] . trim ( ) === '' ) cells . pop ( ) ;
72+ return cells ;
7373}
7474
7575function isSep ( row : string ) {
7676 // A separator row contains only pipes, dashes, colons, and spaces, and has
7777 // at least one dash.
78- if ( ! / \| / . test ( row ) ) return false
79- if ( ! / - / . test ( row ) ) return false
80- return / ^ [ \s | : \- ] + $ / . test ( row )
78+ if ( ! / \| / . test ( row ) ) return false ;
79+ if ( ! / - / . test ( row ) ) return false ;
80+ return / ^ [ \s | : \- ] + $ / . test ( row ) ;
8181}
8282
8383function check ( file : string ) : Issue [ ] {
84- const src = readFileSync ( path . join ( ROOT , file ) , " utf8" )
85- const lines = src . split ( "\n" )
86- const issues : Issue [ ] = [ ]
87- let fence : string | null = null
84+ const src = readFileSync ( path . join ( ROOT , file ) , ' utf8' ) ;
85+ const lines = src . split ( '\n' ) ;
86+ const issues : Issue [ ] = [ ] ;
87+ let fence : string | null = null ;
8888 for ( let i = 0 ; i < lines . length ; i ++ ) {
89- const line = lines [ i ]
90- const trim = line . trim ( )
89+ const line = lines [ i ] ;
90+ const trim = line . trim ( ) ;
9191 // Track fenced code blocks so we don't lint tables inside them.
92- const fenceMatch = trim . match ( / ^ ( ` { 3 , } | ~ { 3 , } ) / )
92+ const fenceMatch = trim . match ( / ^ ( ` { 3 , } | ~ { 3 , } ) / ) ;
9393 if ( fenceMatch ) {
94- const marker = fenceMatch [ 1 ] [ 0 ]
95- if ( fence === null ) fence = marker
96- else if ( marker === fence ) fence = null
97- continue
94+ const marker = fenceMatch [ 1 ] [ 0 ] ;
95+ if ( fence === null ) fence = marker ;
96+ else if ( marker === fence ) fence = null ;
97+ continue ;
9898 }
99- if ( fence !== null ) continue
100- if ( ! line . trimStart ( ) . startsWith ( "|" ) ) continue
99+ if ( fence !== null ) continue ;
100+ if ( ! line . trimStart ( ) . startsWith ( '|' ) ) continue ;
101101
102102 if ( isSep ( line ) ) {
103- const cells = split ( line . trim ( ) )
103+ const cells = split ( line . trim ( ) ) ;
104104 for ( const cell of cells ) {
105105 // Cells must be exactly ---, :---, ---: or :---: with no surrounding
106106 // whitespace. Anything longer (or with space padding) is column-width
@@ -109,115 +109,119 @@ function check(file: string): Issue[] {
109109 issues . push ( {
110110 file,
111111 line : i + 1 ,
112- kind : " separator" ,
112+ kind : ' separator' ,
113113 detail : `separator cell "${ cell } " is padded or extended — use ---, :---, ---: or :---: with no surrounding spaces` ,
114- } )
115- break
114+ } ) ;
115+ break ;
116116 }
117117 }
118- continue
118+ continue ;
119119 }
120120
121121 // Content row: detect padding (>1 space between content and pipe).
122122 // Only inspect lines that look like table rows: starts and ends with `|`.
123- if ( ! line . trimStart ( ) . startsWith ( "|" ) || ! line . trimEnd ( ) . endsWith ( "|" ) ) continue
124- const cells = split ( line . trim ( ) )
123+ if ( ! line . trimStart ( ) . startsWith ( '|' ) || ! line . trimEnd ( ) . endsWith ( '|' ) ) continue ;
124+ const cells = split ( line . trim ( ) ) ;
125125 for ( const cell of cells ) {
126- if ( cell . trim ( ) === "" ) continue
127- const leading = cell . match ( / ^ * / ) ! [ 0 ] . length
128- const trailing = cell . match ( / * $ / ) ! [ 0 ] . length
126+ if ( cell . trim ( ) === '' ) continue ;
127+ const leading = cell . match ( / ^ * / ) ! [ 0 ] . length ;
128+ const trailing = cell . match ( / * $ / ) ! [ 0 ] . length ;
129129 if ( leading > 1 || trailing > 1 ) {
130130 issues . push ( {
131131 file,
132132 line : i + 1 ,
133- kind : " content" ,
133+ kind : ' content' ,
134134 detail : `content cell "${ cell } " has extra padding — use a single space on each side` ,
135- } )
136- break
135+ } ) ;
136+ break ;
137137 }
138138 }
139139 }
140- return issues
140+ return issues ;
141141}
142142
143143function fixSepCell ( raw : string ) {
144- const t = raw . trim ( )
145- const left = t . startsWith ( ":" )
146- const right = t . endsWith ( ":" )
147- if ( left && right ) return " :---:"
148- if ( left ) return " :---"
149- if ( right ) return " ---:"
150- return " ---"
144+ const t = raw . trim ( ) ;
145+ const left = t . startsWith ( ':' ) ;
146+ const right = t . endsWith ( ':' ) ;
147+ if ( left && right ) return ' :---:' ;
148+ if ( left ) return ' :---' ;
149+ if ( right ) return ' ---:' ;
150+ return ' ---' ;
151151}
152152
153153function rewriteRow ( row : string , separator : boolean ) {
154154 // Preserve leading whitespace of the row itself (table indentation).
155- const indent = row . match ( / ^ \s * / ) ! [ 0 ]
156- const body = row . slice ( indent . length )
157- const cells = split ( body )
158- if ( cells . length === 0 ) return row
159- if ( separator ) return `${ indent } |${ cells . map ( fixSepCell ) . join ( "|" ) } |`
160- return `${ indent } | ${ cells . map ( ( c ) => c . trim ( ) ) . join ( " | " ) } |`
155+ const indent = row . match ( / ^ \s * / ) ! [ 0 ] ;
156+ const body = row . slice ( indent . length ) ;
157+ const cells = split ( body ) ;
158+ if ( cells . length === 0 ) return row ;
159+ if ( separator ) return `${ indent } |${ cells . map ( fixSepCell ) . join ( '|' ) } |` ;
160+ return `${ indent } | ${ cells . map ( c => c . trim ( ) ) . join ( ' | ' ) } |` ;
161161}
162162
163163function fix ( file : string ) {
164- const src = readFileSync ( path . join ( ROOT , file ) , " utf8" )
165- const lines = src . split ( "\n" )
166- let changed = false
167- let fence : string | null = null
164+ const src = readFileSync ( path . join ( ROOT , file ) , ' utf8' ) ;
165+ const lines = src . split ( '\n' ) ;
166+ let changed = false ;
167+ let fence : string | null = null ;
168168 for ( let i = 0 ; i < lines . length ; i ++ ) {
169- const line = lines [ i ]
170- const trim = line . trim ( )
171- const fenceMatch = trim . match ( / ^ ( ` { 3 , } | ~ { 3 , } ) / )
169+ const line = lines [ i ] ;
170+ const trim = line . trim ( ) ;
171+ const fenceMatch = trim . match ( / ^ ( ` { 3 , } | ~ { 3 , } ) / ) ;
172172 if ( fenceMatch ) {
173- const marker = fenceMatch [ 1 ] [ 0 ]
174- if ( fence === null ) fence = marker
175- else if ( marker === fence ) fence = null
176- continue
173+ const marker = fenceMatch [ 1 ] [ 0 ] ;
174+ if ( fence === null ) fence = marker ;
175+ else if ( marker === fence ) fence = null ;
176+ continue ;
177177 }
178- if ( fence !== null ) continue
179- if ( ! line . trimStart ( ) . startsWith ( "|" ) ) continue
180- if ( ! line . trimEnd ( ) . endsWith ( "|" ) ) continue
178+ if ( fence !== null ) continue ;
179+ if ( ! line . trimStart ( ) . startsWith ( '|' ) ) continue ;
180+ if ( ! line . trimEnd ( ) . endsWith ( '|' ) ) continue ;
181181
182- const sep = isSep ( line )
183- const next = rewriteRow ( line , sep )
182+ const sep = isSep ( line ) ;
183+ const next = rewriteRow ( line , sep ) ;
184184 if ( next !== line ) {
185- lines [ i ] = next
186- changed = true
185+ lines [ i ] = next ;
186+ changed = true ;
187187 }
188188 }
189- if ( changed ) writeFileSync ( path . join ( ROOT , file ) , lines . join ( "\n" ) )
190- return changed
189+ if ( changed ) writeFileSync ( path . join ( ROOT , file ) , lines . join ( '\n' ) ) ;
190+ return changed ;
191191}
192192
193- const argv = process . argv . slice ( 2 )
194- const fixFlag = argv . includes ( " --fix" )
195- const paths = argv . filter ( ( a ) => a !== " --fix" )
196- const files = ( paths . length > 0 ? paths : tracked ( ) ) . filter ( ( f ) => ! skip ( f ) )
193+ const argv = process . argv . slice ( 2 ) ;
194+ const fixFlag = argv . includes ( ' --fix' ) ;
195+ const paths = argv . filter ( a => a !== ' --fix' ) ;
196+ const files = ( paths . length > 0 ? paths : tracked ( ) ) . filter ( f => ! skip ( f ) ) ;
197197
198198if ( fixFlag ) {
199- let n = 0
200- for ( const f of files ) if ( fix ( f ) ) n ++
201- console . log ( `check-md-table-padding --fix: rewrote ${ n } file(s).` )
202- process . exit ( 0 )
199+ let n = 0 ;
200+ for ( const f of files ) if ( fix ( f ) ) n ++ ;
201+ console . log ( `check-md-table-padding --fix: rewrote ${ n } file(s).` ) ;
202+ process . exit ( 0 ) ;
203203}
204204
205- const all : Issue [ ] = [ ]
205+ const all : Issue [ ] = [ ] ;
206206for ( const f of files ) {
207- for ( const issue of check ( f ) ) all . push ( issue )
207+ for ( const issue of check ( f ) ) all . push ( issue ) ;
208208}
209209
210210if ( all . length === 0 ) {
211- console . log ( `check-md-table-padding: ${ files . length } file(s) checked, no padded tables found.` )
212- process . exit ( 0 )
211+ console . log ( `check-md-table-padding: ${ files . length } file(s) checked, no padded tables found.` ) ;
212+ process . exit ( 0 ) ;
213213}
214214
215215for ( const i of all ) {
216- console . error ( `${ i . file } :${ i . line } [${ i . kind } ] ${ i . detail } ` )
216+ console . error ( `${ i . file } :${ i . line } [${ i . kind } ] ${ i . detail } ` ) ;
217217}
218- console . error ( "" )
219- console . error ( `Found ${ all . length } padded table row(s) across ${ new Set ( all . map ( ( i ) => i . file ) ) . size } file(s).` )
220- console . error ( "Fix: rewrite the table separator as |---|---| and use single-space padding on content cells." )
221- console . error ( "Or run: bun run script/check-md-table-padding.ts --fix" )
222- console . error ( "See AGENTS.md > Markdown Tables." )
223- process . exit ( 1 )
218+ console . error ( '' ) ;
219+ console . error (
220+ `Found ${ all . length } padded table row(s) across ${ new Set ( all . map ( i => i . file ) ) . size } file(s).`
221+ ) ;
222+ console . error (
223+ 'Fix: rewrite the table separator as |---|---| and use single-space padding on content cells.'
224+ ) ;
225+ console . error ( 'Or run: bun run script/check-md-table-padding.ts --fix' ) ;
226+ console . error ( 'See AGENTS.md > Markdown Tables.' ) ;
227+ process . exit ( 1 ) ;
0 commit comments