@blumintinc/eslint-plugin-blumint 1.20.193 → 1.20.194
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/lib/index.js
CHANGED
|
@@ -16,6 +16,10 @@ const MIN_FENCE_LENGTH = 3;
|
|
|
16
16
|
const MAX_FENCE_INDENT_COLUMNS = 3;
|
|
17
17
|
/** CommonMark advances a tab to the next multiple of four when measuring indent. */
|
|
18
18
|
const TAB_STOP = 4;
|
|
19
|
+
/** The three bullet characters CommonMark accepts for an unordered list item. */
|
|
20
|
+
const BULLET_MARKERS = new Set(['-', '+', '*']);
|
|
21
|
+
/** CommonMark caps an ordered list marker at nine digits before its delimiter. */
|
|
22
|
+
const MAX_ORDERED_MARKER_DIGITS = 9;
|
|
19
23
|
function splitLines(text) {
|
|
20
24
|
const lines = [];
|
|
21
25
|
let start = 0;
|
|
@@ -72,6 +76,106 @@ function readFence(line) {
|
|
|
72
76
|
infoString: line.text.slice(offset + runLength),
|
|
73
77
|
};
|
|
74
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Reads the list marker at `offset`, including the whitespace that separates
|
|
81
|
+
* it from the item's content, or 0 when no marker starts there. CommonMark
|
|
82
|
+
* requires that separator, which is what keeps `*emphasis*` and the thematic
|
|
83
|
+
* break `***` from reading as list items.
|
|
84
|
+
*/
|
|
85
|
+
function readListMarker(text, offset) {
|
|
86
|
+
let cursor = offset;
|
|
87
|
+
const char = text[cursor];
|
|
88
|
+
if (char !== undefined && BULLET_MARKERS.has(char)) {
|
|
89
|
+
cursor += 1;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
let digits = 0;
|
|
93
|
+
while (digits < MAX_ORDERED_MARKER_DIGITS &&
|
|
94
|
+
text[cursor + digits] >= '0' &&
|
|
95
|
+
text[cursor + digits] <= '9') {
|
|
96
|
+
digits += 1;
|
|
97
|
+
}
|
|
98
|
+
const delimiter = text[cursor + digits];
|
|
99
|
+
if (digits === 0 || (delimiter !== '.' && delimiter !== ')')) {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
cursor += digits + 1;
|
|
103
|
+
}
|
|
104
|
+
if (text[cursor] !== ' ' && text[cursor] !== '\t') {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
while (text[cursor] === ' ' || text[cursor] === '\t') {
|
|
108
|
+
cursor += 1;
|
|
109
|
+
}
|
|
110
|
+
return cursor - offset;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reads a fence opened on the same line as one or more list markers, as in
|
|
114
|
+
* "- ```ts". A list item's content begins after its marker, so the backticks
|
|
115
|
+
* open a block there exactly as they would at the head of a line.
|
|
116
|
+
*
|
|
117
|
+
* `readFence` cannot see one, because it stops at the first character that is
|
|
118
|
+
* neither a space nor a tab. Without this the scanner walks INTO such a block
|
|
119
|
+
* and mistakes its closing fence — which is nothing but spaces and backticks —
|
|
120
|
+
* for an opening one, appending a language to literal document content.
|
|
121
|
+
*
|
|
122
|
+
* The block is skipped whole rather than labeled: a fix would have to be
|
|
123
|
+
* written past the marker, and labeling a block this rule cannot delimit at
|
|
124
|
+
* the head of a line is the false-negative direction it deliberately keeps.
|
|
125
|
+
*/
|
|
126
|
+
function readListItemFence(line) {
|
|
127
|
+
let indentColumns = 0;
|
|
128
|
+
let offset = 0;
|
|
129
|
+
let sawMarker = false;
|
|
130
|
+
for (;;) {
|
|
131
|
+
while (offset < line.text.length) {
|
|
132
|
+
const char = line.text[offset];
|
|
133
|
+
if (char === ' ') {
|
|
134
|
+
indentColumns += 1;
|
|
135
|
+
}
|
|
136
|
+
else if (char === '\t') {
|
|
137
|
+
indentColumns += TAB_STOP - (indentColumns % TAB_STOP);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
offset += 1;
|
|
143
|
+
}
|
|
144
|
+
if (indentColumns > MAX_FENCE_INDENT_COLUMNS) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const markerLength = readListMarker(line.text, offset);
|
|
148
|
+
if (markerLength === 0) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
offset += markerLength;
|
|
152
|
+
// The item's content starts a fresh indent budget, so a fence sitting at
|
|
153
|
+
// the content column is at indent zero however deep the nesting is.
|
|
154
|
+
indentColumns = 0;
|
|
155
|
+
sawMarker = true;
|
|
156
|
+
}
|
|
157
|
+
if (!sawMarker) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const marker = line.text[offset];
|
|
161
|
+
if (marker !== BACKTICK && marker !== TILDE) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
let runLength = 0;
|
|
165
|
+
while (line.text[offset + runLength] === marker) {
|
|
166
|
+
runLength += 1;
|
|
167
|
+
}
|
|
168
|
+
if (runLength < MIN_FENCE_LENGTH) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
runStart: line.start + offset,
|
|
173
|
+
marker,
|
|
174
|
+
runLength,
|
|
175
|
+
indent: line.text.slice(0, offset),
|
|
176
|
+
infoString: line.text.slice(offset + runLength),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
75
179
|
/**
|
|
76
180
|
* The line a block closes on, per CommonMark: a run of at least the opening
|
|
77
181
|
* length, of the SAME marker, at a fence indent, with nothing but whitespace
|
|
@@ -131,7 +235,21 @@ exports.enforceTypescriptMarkdownCodeBlocks = (0, createRule_1.createRule)({
|
|
|
131
235
|
const openingLine = lines[index];
|
|
132
236
|
const fence = readFence(openingLine);
|
|
133
237
|
if (fence === null) {
|
|
134
|
-
|
|
238
|
+
const listFence = readListItemFence(openingLine);
|
|
239
|
+
if (listFence === null) {
|
|
240
|
+
index += 1;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// A fence opened on a list marker's line is one this rule declines
|
|
244
|
+
// to label, and declining to label a block means declining to read
|
|
245
|
+
// it. Skipping it whole is what keeps its closing fence — spaces
|
|
246
|
+
// and backticks, indistinguishable from an opener — out of the
|
|
247
|
+
// walk.
|
|
248
|
+
const listClosing = findFenceCloser(lines, index + 1, listFence);
|
|
249
|
+
if (listClosing === null) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
index = listClosing.line + 1;
|
|
135
253
|
continue;
|
|
136
254
|
}
|
|
137
255
|
const closing = findFenceCloser(lines, index + 1, fence);
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.194",
|
|
4
|
+
"date": "2026-08-30T09:11:52.116Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-typescript-markdown-code-blocks",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2220
|
|
11
|
+
],
|
|
12
|
+
"summary": "skip a fence opened on a list marker's line (closes #2220)"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"version": "1.20.193",
|
|
4
18
|
"date": "2026-08-30T05:32:04.963Z",
|