@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
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.193',
226
+ version: '1.20.194',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -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
- index += 1;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.193",
3
+ "version": "1.20.194",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -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",