pickier 0.1.15 → 0.1.16

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/dist/bin/cli.js CHANGED
@@ -353,13 +353,18 @@ function detectQuoteIssues(line, preferred) {
353
353
  }
354
354
  return indices;
355
355
  }
356
- function hasIndentIssue(leading, indentSize, indentStyle = "spaces") {
356
+ function hasIndentIssue(leading, indentSize, indentStyle = "spaces", lineContent) {
357
357
  if (indentStyle === "tabs") {
358
358
  return /[^\t]/.test(leading);
359
359
  }
360
360
  if (/\t/.test(leading))
361
361
  return true;
362
362
  const spaces = leading.length;
363
+ if (lineContent && spaces % indentSize === 1) {
364
+ const trimmed = lineContent.trimStart();
365
+ if (trimmed.startsWith("* ") || trimmed.startsWith("*/") || trimmed === "*")
366
+ return false;
367
+ }
363
368
  return spaces % indentSize !== 0;
364
369
  }
365
370
  function maskStrings(input) {
@@ -17157,38 +17162,41 @@ var init_blanks_around_fences = __esm(() => {
17157
17162
  check: (text, ctx) => {
17158
17163
  const issues = [];
17159
17164
  const lines = text.split(/\r?\n/);
17165
+ let inFence = false;
17160
17166
  for (let i = 0;i < lines.length; i++) {
17161
17167
  const line = lines[i];
17162
- const prevLine = i > 0 ? lines[i - 1] : "";
17163
- const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
17164
- const isFence = /^(`{3,}|~{3,})/.test(line);
17168
+ const isFence = /^(`{3,}|~{3,})/.test(line.trim());
17165
17169
  if (isFence) {
17166
- let isOpening = true;
17167
- for (let j = i - 1;j >= 0; j--) {
17168
- if (/^(`{3,}|~{3,})/.test(lines[j])) {
17169
- isOpening = false;
17170
- break;
17170
+ if (!inFence) {
17171
+ const prevLine = i > 0 ? lines[i - 1] : "";
17172
+ if (i > 0 && prevLine.trim().length > 0) {
17173
+ if (!/^:::/.test(prevLine.trim())) {
17174
+ issues.push({
17175
+ filePath: ctx.filePath,
17176
+ line: i + 1,
17177
+ column: 1,
17178
+ ruleId: "markdown/blanks-around-fences",
17179
+ message: "Fenced code blocks should be surrounded by blank lines",
17180
+ severity: "error"
17181
+ });
17182
+ }
17171
17183
  }
17172
- }
17173
- if (isOpening && i > 0 && prevLine.trim().length > 0) {
17174
- issues.push({
17175
- filePath: ctx.filePath,
17176
- line: i + 1,
17177
- column: 1,
17178
- ruleId: "markdown/blanks-around-fences",
17179
- message: "Fenced code blocks should be surrounded by blank lines",
17180
- severity: "error"
17181
- });
17182
- }
17183
- if (!isOpening && i + 1 < lines.length && nextLine.trim().length > 0) {
17184
- issues.push({
17185
- filePath: ctx.filePath,
17186
- line: i + 1,
17187
- column: 1,
17188
- ruleId: "markdown/blanks-around-fences",
17189
- message: "Fenced code blocks should be surrounded by blank lines",
17190
- severity: "error"
17191
- });
17184
+ inFence = true;
17185
+ } else {
17186
+ const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
17187
+ if (i + 1 < lines.length && nextLine.trim().length > 0) {
17188
+ if (!/^:::/.test(nextLine.trim())) {
17189
+ issues.push({
17190
+ filePath: ctx.filePath,
17191
+ line: i + 1,
17192
+ column: 1,
17193
+ ruleId: "markdown/blanks-around-fences",
17194
+ message: "Fenced code blocks should be surrounded by blank lines",
17195
+ severity: "error"
17196
+ });
17197
+ }
17198
+ }
17199
+ inFence = false;
17192
17200
  }
17193
17201
  }
17194
17202
  }
@@ -17201,10 +17209,10 @@ var init_blanks_around_fences = __esm(() => {
17201
17209
  for (let i = 0;i < lines.length; i++) {
17202
17210
  const line = lines[i];
17203
17211
  const prevLine = i > 0 ? lines[i - 1] : "";
17204
- const isFence = /^(`{3,}|~{3,})/.test(line);
17212
+ const isFence = /^(`{3,}|~{3,})/.test(line.trim());
17205
17213
  if (isFence) {
17206
17214
  if (!inFence) {
17207
- if (i > 0 && prevLine.trim().length > 0 && result.length > 0) {
17215
+ if (i > 0 && prevLine.trim().length > 0 && !/^:::/.test(prevLine.trim()) && result.length > 0) {
17208
17216
  result.push("");
17209
17217
  }
17210
17218
  inFence = true;
@@ -17215,7 +17223,7 @@ var init_blanks_around_fences = __esm(() => {
17215
17223
  result.push(line);
17216
17224
  if (isFence && !inFence && i + 1 < lines.length) {
17217
17225
  const nextLine = lines[i + 1];
17218
- if (nextLine.trim().length > 0) {
17226
+ if (nextLine.trim().length > 0 && !/^:::/.test(nextLine.trim())) {
17219
17227
  result.push("");
17220
17228
  }
17221
17229
  }
@@ -17351,14 +17359,22 @@ var init_blanks_around_lists = __esm(() => {
17351
17359
  const issues = [];
17352
17360
  const lines = text.split(/\r?\n/);
17353
17361
  let inList = false;
17354
- let listStartLine = -1;
17362
+ let inFence = false;
17355
17363
  for (let i = 0;i < lines.length; i++) {
17356
17364
  const line = lines[i];
17357
17365
  const prevLine = i > 0 ? lines[i - 1] : "";
17366
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17367
+ inFence = !inFence;
17368
+ if (inList)
17369
+ inList = false;
17370
+ continue;
17371
+ }
17372
+ if (inFence)
17373
+ continue;
17358
17374
  const isListItem = /^(\s*)([*\-+]|\d+\.)\s+/.test(line);
17375
+ const isListContinuation = inList && !isListItem && line.trim().length > 0 && /^\s+/.test(line);
17359
17376
  if (isListItem && !inList) {
17360
17377
  inList = true;
17361
- listStartLine = i;
17362
17378
  if (i > 0 && prevLine.trim().length > 0) {
17363
17379
  issues.push({
17364
17380
  filePath: ctx.filePath,
@@ -17369,7 +17385,7 @@ var init_blanks_around_lists = __esm(() => {
17369
17385
  severity: "error"
17370
17386
  });
17371
17387
  }
17372
- } else if (!isListItem && inList && line.trim().length > 0) {
17388
+ } else if (!isListItem && !isListContinuation && inList && line.trim().length > 0) {
17373
17389
  inList = false;
17374
17390
  const prevLineIsListItem = /^(\s*)([*\-+]|\d+\.)\s+/.test(prevLine);
17375
17391
  if (prevLineIsListItem) {
@@ -17430,15 +17446,21 @@ var init_blanks_around_tables = __esm(() => {
17430
17446
  const issues = [];
17431
17447
  const lines = text.split(/\r?\n/);
17432
17448
  let inTable = false;
17433
- let tableStartLine = -1;
17449
+ let inFence = false;
17434
17450
  for (let i = 0;i < lines.length; i++) {
17435
17451
  const line = lines[i];
17436
17452
  const prevLine = i > 0 ? lines[i - 1] : "";
17437
- const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
17453
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17454
+ inFence = !inFence;
17455
+ if (inTable)
17456
+ inTable = false;
17457
+ continue;
17458
+ }
17459
+ if (inFence)
17460
+ continue;
17438
17461
  const isTableLine = /\|/.test(line) && line.trim().length > 0;
17439
17462
  if (isTableLine && !inTable) {
17440
17463
  inTable = true;
17441
- tableStartLine = i;
17442
17464
  if (i > 0 && prevLine.trim().length > 0) {
17443
17465
  issues.push({
17444
17466
  filePath: ctx.filePath,
@@ -17920,8 +17942,15 @@ var init_heading_increment = __esm(() => {
17920
17942
  const issues = [];
17921
17943
  const lines = text.split(/\r?\n/);
17922
17944
  let previousLevel = 0;
17945
+ let inFence = false;
17923
17946
  for (let i = 0;i < lines.length; i++) {
17924
17947
  const line = lines[i];
17948
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17949
+ inFence = !inFence;
17950
+ continue;
17951
+ }
17952
+ if (inFence)
17953
+ continue;
17925
17954
  const atxMatch = line.match(/^(#{1,6})\s/);
17926
17955
  if (atxMatch) {
17927
17956
  const level = atxMatch[1].length;
@@ -17953,8 +17982,15 @@ var init_heading_start_left = __esm(() => {
17953
17982
  check: (text, ctx) => {
17954
17983
  const issues = [];
17955
17984
  const lines = text.split(/\r?\n/);
17985
+ let inFence = false;
17956
17986
  for (let i = 0;i < lines.length; i++) {
17957
17987
  const line = lines[i];
17988
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17989
+ inFence = !inFence;
17990
+ continue;
17991
+ }
17992
+ if (inFence)
17993
+ continue;
17958
17994
  const match = line.match(/^(\s+)(#{1,6}\s)/);
17959
17995
  if (match) {
17960
17996
  issues.push({
@@ -18249,8 +18285,24 @@ var init_link_image_style = __esm(() => {
18249
18285
  const options = ctx.options || {};
18250
18286
  const style = options.style || "consistent";
18251
18287
  let detectedStyle = null;
18288
+ let inFence = false;
18289
+ let inHtmlComment = false;
18252
18290
  for (let i = 0;i < lines.length; i++) {
18253
18291
  const line = lines[i];
18292
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18293
+ inFence = !inFence;
18294
+ continue;
18295
+ }
18296
+ if (inFence)
18297
+ continue;
18298
+ if (line.includes("<!--"))
18299
+ inHtmlComment = true;
18300
+ if (line.includes("-->")) {
18301
+ inHtmlComment = false;
18302
+ continue;
18303
+ }
18304
+ if (inHtmlComment)
18305
+ continue;
18254
18306
  if (line.match(/^\[([^\]]+)\]:\s*\S+/)) {
18255
18307
  continue;
18256
18308
  }
@@ -18467,19 +18519,34 @@ var init_no_bare_urls = __esm(() => {
18467
18519
  check: (text, ctx) => {
18468
18520
  const issues = [];
18469
18521
  const lines = text.split(/\r?\n/);
18522
+ let inFence = false;
18523
+ let inHtmlComment = false;
18470
18524
  for (let i = 0;i < lines.length; i++) {
18471
18525
  const line = lines[i];
18472
- if (line.trim().startsWith("```") || line.trim().startsWith("~~~")) {
18526
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18527
+ inFence = !inFence;
18528
+ continue;
18529
+ }
18530
+ if (inFence)
18531
+ continue;
18532
+ if (line.includes("<!--"))
18533
+ inHtmlComment = true;
18534
+ if (line.includes("-->")) {
18535
+ inHtmlComment = false;
18473
18536
  continue;
18474
18537
  }
18475
- const urlPattern = /(?<![<`(])https?:\/\/[^\s<>`)\]]+(?![>\])`])/g;
18476
- const matches = line.matchAll(urlPattern);
18538
+ if (inHtmlComment)
18539
+ continue;
18540
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
18541
+ continue;
18542
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
18543
+ const urlPattern = /(?<![<(="'])https?:\/\/[^\s<>`)\]"']+(?![>\])"'])/g;
18544
+ const matches = stripped.matchAll(urlPattern);
18477
18545
  for (const match of matches) {
18478
- const column = match.index + 1;
18479
18546
  issues.push({
18480
18547
  filePath: ctx.filePath,
18481
18548
  line: i + 1,
18482
- column,
18549
+ column: match.index + 1,
18483
18550
  ruleId: "markdown/no-bare-urls",
18484
18551
  message: "Bare URL used. Wrap in angle brackets: <url>",
18485
18552
  severity: "error"
@@ -18490,11 +18557,26 @@ var init_no_bare_urls = __esm(() => {
18490
18557
  },
18491
18558
  fix: (text) => {
18492
18559
  const lines = text.split(/\r?\n/);
18560
+ let inFence = false;
18561
+ let inHtmlComment = false;
18493
18562
  const fixedLines = lines.map((line) => {
18494
- if (line.trim().startsWith("```") || line.trim().startsWith("~~~")) {
18563
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18564
+ inFence = !inFence;
18495
18565
  return line;
18496
18566
  }
18497
- return line.replace(/(?<![<`(])https?:\/\/[^\s<>`)\]]+(?![>\])`])/g, "<$&>");
18567
+ if (inFence)
18568
+ return line;
18569
+ if (line.includes("<!--"))
18570
+ inHtmlComment = true;
18571
+ if (line.includes("-->")) {
18572
+ inHtmlComment = false;
18573
+ return line;
18574
+ }
18575
+ if (inHtmlComment)
18576
+ return line;
18577
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
18578
+ return line;
18579
+ return line.replace(/(?<![<(="'])https?:\/\/[^\s<>`)\]"']+(?![>\])"'])/g, "<$&>");
18498
18580
  });
18499
18581
  return fixedLines.join(`
18500
18582
  `);
@@ -18550,8 +18632,15 @@ var init_no_duplicate_heading = __esm(() => {
18550
18632
  const issues = [];
18551
18633
  const lines = text.split(/\r?\n/);
18552
18634
  const headings = new Map;
18635
+ let inFence = false;
18553
18636
  for (let i = 0;i < lines.length; i++) {
18554
18637
  const line = lines[i];
18638
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18639
+ inFence = !inFence;
18640
+ continue;
18641
+ }
18642
+ if (inFence)
18643
+ continue;
18555
18644
  const atxMatch = line.match(/^#{1,6}\s+(.+?)(?:\s*#+\s*)?$/);
18556
18645
  if (atxMatch) {
18557
18646
  const content = atxMatch[1].trim();
@@ -18600,10 +18689,17 @@ var init_no_emphasis_as_heading = __esm(() => {
18600
18689
  check: (text, ctx) => {
18601
18690
  const issues = [];
18602
18691
  const lines = text.split(/\r?\n/);
18692
+ let inFence = false;
18603
18693
  for (let i = 0;i < lines.length; i++) {
18604
18694
  const line = lines[i];
18605
18695
  const prevLine = i > 0 ? lines[i - 1] : "";
18606
18696
  const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
18697
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18698
+ inFence = !inFence;
18699
+ continue;
18700
+ }
18701
+ if (inFence)
18702
+ continue;
18607
18703
  const isBoldLine = /^\*\*[^*]+\*\*\s*$/.test(line) || /^__[^_]+__\s*$/.test(line);
18608
18704
  const isItalicLine = /^\*[^*]+\*\s*$/.test(line) || /^_[^_]+_\s*$/.test(line);
18609
18705
  const isStandalone = prevLine.trim().length === 0 && nextLine.trim().length === 0;
@@ -18709,9 +18805,17 @@ var init_no_inline_html = __esm(() => {
18709
18805
  const lines = text.split(/\r?\n/);
18710
18806
  const options = ctx.options || {};
18711
18807
  const allowedElements = options.allowed_elements || [];
18808
+ let inFence = false;
18712
18809
  for (let i = 0;i < lines.length; i++) {
18713
18810
  const line = lines[i];
18714
- const matches = line.matchAll(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi);
18811
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18812
+ inFence = !inFence;
18813
+ continue;
18814
+ }
18815
+ if (inFence)
18816
+ continue;
18817
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
18818
+ const matches = stripped.matchAll(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi);
18715
18819
  for (const match of matches) {
18716
18820
  const tagName = match[1].toLowerCase();
18717
18821
  if (!allowedElements.includes(tagName)) {
@@ -19027,27 +19131,54 @@ var init_no_space_in_code = __esm(() => {
19027
19131
  check: (text, ctx) => {
19028
19132
  const issues = [];
19029
19133
  const lines = text.split(/\r?\n/);
19134
+ let inFence = false;
19030
19135
  for (let i = 0;i < lines.length; i++) {
19031
19136
  const line = lines[i];
19032
- const matches = line.matchAll(/(`+)\s+([^`]+?)\s+\1/g);
19033
- for (const match of matches) {
19034
- const column = match.index + 1;
19035
- issues.push({
19036
- filePath: ctx.filePath,
19037
- line: i + 1,
19038
- column,
19039
- ruleId: "markdown/no-space-in-code",
19040
- message: "Spaces inside code span elements",
19041
- severity: "error"
19042
- });
19137
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19138
+ inFence = !inFence;
19139
+ continue;
19140
+ }
19141
+ if (inFence)
19142
+ continue;
19143
+ const codeSpanPattern = /(`+)([\s\S]*?)\1/g;
19144
+ for (const match of line.matchAll(codeSpanPattern)) {
19145
+ const content = match[2];
19146
+ if (content.startsWith(" ") && content.endsWith(" ") && content.trim().length > 0) {
19147
+ issues.push({
19148
+ filePath: ctx.filePath,
19149
+ line: i + 1,
19150
+ column: match.index + 1,
19151
+ ruleId: "markdown/no-space-in-code",
19152
+ message: "Spaces inside code span elements",
19153
+ severity: "error"
19154
+ });
19155
+ }
19043
19156
  }
19044
19157
  }
19045
19158
  return issues;
19046
19159
  },
19047
19160
  fix: (text) => {
19048
- return text.replace(/(`+)\s+([^`]+?)\s+\1/g, (match, backticks, content) => {
19049
- return `${backticks}${content.trim()}${backticks}`;
19050
- });
19161
+ const lines = text.split(/\r?\n/);
19162
+ let inFence = false;
19163
+ const result = [];
19164
+ for (const line of lines) {
19165
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19166
+ inFence = !inFence;
19167
+ result.push(line);
19168
+ continue;
19169
+ }
19170
+ if (inFence) {
19171
+ result.push(line);
19172
+ continue;
19173
+ }
19174
+ result.push(line.replace(/(`+)([\s\S]*?)\1/g, (match, backticks, content) => {
19175
+ if (content.startsWith(" ") && content.endsWith(" ") && content.trim().length > 0)
19176
+ return `${backticks}${content.trim()}${backticks}`;
19177
+ return match;
19178
+ }));
19179
+ }
19180
+ return result.join(`
19181
+ `);
19051
19182
  }
19052
19183
  };
19053
19184
  });
@@ -19062,20 +19193,28 @@ var init_no_space_in_emphasis = __esm(() => {
19062
19193
  check: (text, ctx) => {
19063
19194
  const issues = [];
19064
19195
  const lines = text.split(/\r?\n/);
19196
+ let inFence = false;
19065
19197
  for (let i = 0;i < lines.length; i++) {
19066
19198
  const line = lines[i];
19199
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19200
+ inFence = !inFence;
19201
+ continue;
19202
+ }
19203
+ if (inFence)
19204
+ continue;
19205
+ const stripped = line.replace(/`[^`]+`/g, (m) => "\x01".repeat(m.length));
19067
19206
  const patterns = [
19068
- /(\*\*|\*|__?)\s+/g,
19069
- /\s+(\*\*|\*|__?)/g
19207
+ /\*\*\s+[^*]+?\*\*/g,
19208
+ /\*\*[^*]+?\s+\*\*/g,
19209
+ /__\s+[^_]+?__/g,
19210
+ /__[^_]+?\s+__/g
19070
19211
  ];
19071
19212
  for (const pattern of patterns) {
19072
- const matches = line.matchAll(pattern);
19073
- for (const match of matches) {
19074
- const column = match.index + 1;
19213
+ for (const match of stripped.matchAll(pattern)) {
19075
19214
  issues.push({
19076
19215
  filePath: ctx.filePath,
19077
19216
  line: i + 1,
19078
- column,
19217
+ column: match.index + 1,
19079
19218
  ruleId: "markdown/no-space-in-emphasis",
19080
19219
  message: "Spaces inside emphasis markers",
19081
19220
  severity: "error"
@@ -19086,9 +19225,28 @@ var init_no_space_in_emphasis = __esm(() => {
19086
19225
  return issues;
19087
19226
  },
19088
19227
  fix: (text) => {
19089
- let fixed = text.replace(/(\*\*|\*|__?)\s+/g, "$1");
19090
- fixed = fixed.replace(/\s+(\*\*|\*|__?)/g, "$1");
19091
- return fixed;
19228
+ const lines = text.split(/\r?\n/);
19229
+ let inFence = false;
19230
+ const result = [];
19231
+ for (const line of lines) {
19232
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19233
+ inFence = !inFence;
19234
+ result.push(line);
19235
+ continue;
19236
+ }
19237
+ if (inFence) {
19238
+ result.push(line);
19239
+ continue;
19240
+ }
19241
+ let fixed = line;
19242
+ fixed = fixed.replace(/\*\*\s+([^*]+?)\*\*/g, "**$1**");
19243
+ fixed = fixed.replace(/\*\*([^*]+?)\s+\*\*/g, "**$1**");
19244
+ fixed = fixed.replace(/__\s+([^_]+?)__/g, "__$1__");
19245
+ fixed = fixed.replace(/__([^_]+?)\s+__/g, "__$1__");
19246
+ result.push(fixed);
19247
+ }
19248
+ return result.join(`
19249
+ `);
19092
19250
  }
19093
19251
  };
19094
19252
  });
@@ -19392,20 +19550,37 @@ var init_reference_links_images = __esm(() => {
19392
19550
  definitions.add(defMatch[1].toLowerCase());
19393
19551
  }
19394
19552
  }
19553
+ let inFence = false;
19554
+ let inHtmlComment = false;
19395
19555
  for (let i = 0;i < lines.length; i++) {
19396
19556
  const line = lines[i];
19397
- const linkMatches = line.matchAll(/\[([^\]]+)\](?:\[([^\]]+)\])?(?!\()/g);
19557
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19558
+ inFence = !inFence;
19559
+ continue;
19560
+ }
19561
+ if (inFence)
19562
+ continue;
19563
+ if (line.includes("<!--"))
19564
+ inHtmlComment = true;
19565
+ if (line.includes("-->")) {
19566
+ inHtmlComment = false;
19567
+ continue;
19568
+ }
19569
+ if (inHtmlComment)
19570
+ continue;
19571
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
19572
+ continue;
19573
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
19574
+ const linkMatches = stripped.matchAll(/\[([^\]]+)\](?:\[([^\]]+)\])?(?!\()/g);
19398
19575
  for (const match of linkMatches) {
19399
19576
  const label = (match[2] || match[1]).toLowerCase();
19400
- if (line.match(/^\[([^\]]+)\]:\s*\S+/)) {
19577
+ if (/^[xX ]$/.test(label))
19401
19578
  continue;
19402
- }
19403
19579
  if (!definitions.has(label)) {
19404
- const column = match.index + 1;
19405
19580
  issues.push({
19406
19581
  filePath: ctx.filePath,
19407
19582
  line: i + 1,
19408
- column,
19583
+ column: match.index + 1,
19409
19584
  ruleId: "markdown/reference-links-images",
19410
19585
  message: `Reference link '[${label}]' is not defined`,
19411
19586
  severity: "error"
@@ -19748,8 +19923,15 @@ var init_table_pipe_style = __esm(() => {
19748
19923
  const lines = text.split(/\r?\n/);
19749
19924
  const options = ctx.options || {};
19750
19925
  const style = options.style || "leading_and_trailing";
19926
+ let inFence = false;
19751
19927
  for (let i = 0;i < lines.length; i++) {
19752
19928
  const line = lines[i];
19929
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19930
+ inFence = !inFence;
19931
+ continue;
19932
+ }
19933
+ if (inFence)
19934
+ continue;
19753
19935
  if (/\|/.test(line) && line.trim().length > 0) {
19754
19936
  const hasLeading = line.trim().startsWith("|");
19755
19937
  const hasTrailing = line.trim().endsWith("|");
@@ -19817,8 +19999,15 @@ var init_ul_indent = __esm(() => {
19817
19999
  const lines = text.split(/\r?\n/);
19818
20000
  const options = ctx.options || {};
19819
20001
  const expectedIndent = options.indent || 2;
20002
+ let inFence = false;
19820
20003
  for (let i = 0;i < lines.length; i++) {
19821
20004
  const line = lines[i];
20005
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
20006
+ inFence = !inFence;
20007
+ continue;
20008
+ }
20009
+ if (inFence)
20010
+ continue;
19822
20011
  const match = line.match(/^(\s*)([*\-+])\s+/);
19823
20012
  if (match) {
19824
20013
  const indent = match[1].length;
@@ -19932,18 +20121,49 @@ var init_ul_style = __esm(() => {
19932
20121
  });
19933
20122
 
19934
20123
  // src/plugins/markdown.ts
20124
+ function stripFrontmatter(content) {
20125
+ const lines = content.split(/\r?\n/);
20126
+ if (lines[0]?.trim() !== "---")
20127
+ return { text: content, frontmatter: null, frontmatterEndLine: 0 };
20128
+ for (let i = 1;i < lines.length; i++) {
20129
+ if (lines[i].trim() === "---") {
20130
+ const frontmatter = lines.slice(0, i + 1);
20131
+ const blanked = frontmatter.map(() => "");
20132
+ return {
20133
+ text: [...blanked, ...lines.slice(i + 1)].join(`
20134
+ `),
20135
+ frontmatter,
20136
+ frontmatterEndLine: i + 1
20137
+ };
20138
+ }
20139
+ }
20140
+ return { text: content, frontmatter: null, frontmatterEndLine: 0 };
20141
+ }
20142
+ function restoreFrontmatter(content, frontmatter) {
20143
+ if (!frontmatter)
20144
+ return content;
20145
+ const lines = content.split(/\r?\n/);
20146
+ return [...frontmatter, ...lines.slice(frontmatter.length)].join(`
20147
+ `);
20148
+ }
19935
20149
  function markdownOnly(rule) {
19936
20150
  return {
19937
20151
  meta: rule.meta,
19938
20152
  check: (content, context) => {
19939
20153
  if (!context.filePath.endsWith(".md"))
19940
20154
  return [];
19941
- return rule.check(content, context);
20155
+ const { text, frontmatterEndLine } = stripFrontmatter(content);
20156
+ const issues = rule.check(text, context);
20157
+ if (frontmatterEndLine > 0)
20158
+ return issues.filter((issue) => issue.line > frontmatterEndLine + 1);
20159
+ return issues;
19942
20160
  },
19943
20161
  fix: rule.fix ? (content, context) => {
19944
20162
  if (!context.filePath.endsWith(".md"))
19945
20163
  return content;
19946
- return rule.fix(content, context);
20164
+ const { text, frontmatter } = stripFrontmatter(content);
20165
+ const fixed = rule.fix(text, context);
20166
+ return restoreFrontmatter(fixed, frontmatter);
19947
20167
  } : undefined
19948
20168
  };
19949
20169
  }
@@ -25632,13 +25852,13 @@ var init_style = __esm(() => {
25632
25852
  stylePlugin = {
25633
25853
  name: "style",
25634
25854
  rules: {
25635
- "brace-style": braceStyle,
25636
- curly: curlyRule,
25637
- "max-statements-per-line": maxStatementsPerLineRule,
25638
- "if-newline": ifNewlineRule,
25639
- "consistent-chaining": consistentChainingRule,
25640
- "consistent-list-newline": consistentListNewlineRule,
25641
- "indent-unindent": indentUnindentRule,
25855
+ "brace-style": codeOnly(braceStyle),
25856
+ curly: codeOnly(curlyRule),
25857
+ "max-statements-per-line": codeOnly(maxStatementsPerLineRule),
25858
+ "if-newline": codeOnly(ifNewlineRule),
25859
+ "consistent-chaining": codeOnly(consistentChainingRule),
25860
+ "consistent-list-newline": codeOnly(consistentListNewlineRule),
25861
+ "indent-unindent": codeOnly(indentUnindentRule),
25642
25862
  "no-multi-spaces": noMultiSpaces,
25643
25863
  "no-multiple-empty-lines": noMultipleEmptyLines,
25644
25864
  "no-trailing-spaces": noTrailingSpaces,
@@ -28052,6 +28272,18 @@ function scanContentOptimized(filePath, content, cfg, suppress, commentLines) {
28052
28272
  const wantNoCondAssign = sevMap(cfg.rules.noCondAssign);
28053
28273
  const consoleCall = /\bconsole\.log\s*\(/;
28054
28274
  const debuggerStmt = /^\s*debugger\b/;
28275
+ const linesInFencedCodeBlock = new Set;
28276
+ if (fileExt === "md") {
28277
+ let inFence = false;
28278
+ for (let li = 0;li < lines.length; li++) {
28279
+ if (/^(`{3,}|~{3,})/.test(lines[li].trim())) {
28280
+ inFence = !inFence;
28281
+ continue;
28282
+ }
28283
+ if (inFence)
28284
+ linesInFencedCodeBlock.add(li + 1);
28285
+ }
28286
+ }
28055
28287
  const linesInTemplate = new Set;
28056
28288
  let inTemplate = false;
28057
28289
  let escaped = false;
@@ -28100,7 +28332,7 @@ function scanContentOptimized(filePath, content, cfg, suppress, commentLines) {
28100
28332
  }
28101
28333
  const leadingMatch = line.match(/^[ \t]*/);
28102
28334
  const leading = leadingMatch ? leadingMatch[0] : "";
28103
- if (leading.length > 0 && hasIndentIssue(leading, cfg.format.indent, cfg.format.indentStyle)) {
28335
+ if (leading.length > 0 && !linesInFencedCodeBlock.has(lineNo) && hasIndentIssue(leading, cfg.format.indent, cfg.format.indentStyle, line)) {
28104
28336
  if (!isSuppressed("indent", lineNo, suppress))
28105
28337
  issues.push({ filePath, line: lineNo, column: 1, ruleId: "indent", message: "Incorrect indentation detected", severity: "warning", help: `Use ${cfg.format.indentStyle === "spaces" ? `${cfg.format.indent} spaces` : "tabs"} for indentation. Configure with format.indent and format.indentStyle in your config` });
28106
28338
  }
@@ -32446,7 +32678,7 @@ var require_package = __commonJS((exports, module) => {
32446
32678
  module.exports = {
32447
32679
  name: "pickier",
32448
32680
  type: "module",
32449
- version: "0.1.15",
32681
+ version: "0.1.16",
32450
32682
  description: "Format, lint and more in a fraction of seconds.",
32451
32683
  author: "Chris Breuer <chris@stacksjs.org>",
32452
32684
  license: "MIT",
package/dist/format.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PickierConfig } from './types';
2
2
  export declare function formatCode(src: string, cfg: PickierConfig, filePath: string): string;
3
3
  export declare function detectQuoteIssues(line: string, preferred: 'single' | 'double'): number[];
4
- export declare function hasIndentIssue(leading: string, indentSize: number, indentStyle?: 'spaces' | 'tabs'): boolean;
4
+ export declare function hasIndentIssue(leading: string, indentSize: number, indentStyle?: 'spaces' | 'tabs', lineContent?: string): boolean;
5
5
  export declare function formatImports(source: string): string;
6
6
  declare type ImportKind = 'value' | 'type' | 'side-effect'
package/dist/src/index.js CHANGED
@@ -5591,13 +5591,18 @@ function detectQuoteIssues(line, preferred) {
5591
5591
  }
5592
5592
  return indices;
5593
5593
  }
5594
- function hasIndentIssue(leading, indentSize, indentStyle = "spaces") {
5594
+ function hasIndentIssue(leading, indentSize, indentStyle = "spaces", lineContent) {
5595
5595
  if (indentStyle === "tabs") {
5596
5596
  return /[^\t]/.test(leading);
5597
5597
  }
5598
5598
  if (/\t/.test(leading))
5599
5599
  return true;
5600
5600
  const spaces = leading.length;
5601
+ if (lineContent && spaces % indentSize === 1) {
5602
+ const trimmed = lineContent.trimStart();
5603
+ if (trimmed.startsWith("* ") || trimmed.startsWith("*/") || trimmed === "*")
5604
+ return false;
5605
+ }
5601
5606
  return spaces % indentSize !== 0;
5602
5607
  }
5603
5608
  function maskStrings(input) {
@@ -16806,38 +16811,41 @@ var init_blanks_around_fences = __esm(() => {
16806
16811
  check: (text, ctx) => {
16807
16812
  const issues = [];
16808
16813
  const lines = text.split(/\r?\n/);
16814
+ let inFence = false;
16809
16815
  for (let i = 0;i < lines.length; i++) {
16810
16816
  const line = lines[i];
16811
- const prevLine = i > 0 ? lines[i - 1] : "";
16812
- const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
16813
- const isFence = /^(`{3,}|~{3,})/.test(line);
16817
+ const isFence = /^(`{3,}|~{3,})/.test(line.trim());
16814
16818
  if (isFence) {
16815
- let isOpening = true;
16816
- for (let j = i - 1;j >= 0; j--) {
16817
- if (/^(`{3,}|~{3,})/.test(lines[j])) {
16818
- isOpening = false;
16819
- break;
16819
+ if (!inFence) {
16820
+ const prevLine = i > 0 ? lines[i - 1] : "";
16821
+ if (i > 0 && prevLine.trim().length > 0) {
16822
+ if (!/^:::/.test(prevLine.trim())) {
16823
+ issues.push({
16824
+ filePath: ctx.filePath,
16825
+ line: i + 1,
16826
+ column: 1,
16827
+ ruleId: "markdown/blanks-around-fences",
16828
+ message: "Fenced code blocks should be surrounded by blank lines",
16829
+ severity: "error"
16830
+ });
16831
+ }
16820
16832
  }
16821
- }
16822
- if (isOpening && i > 0 && prevLine.trim().length > 0) {
16823
- issues.push({
16824
- filePath: ctx.filePath,
16825
- line: i + 1,
16826
- column: 1,
16827
- ruleId: "markdown/blanks-around-fences",
16828
- message: "Fenced code blocks should be surrounded by blank lines",
16829
- severity: "error"
16830
- });
16831
- }
16832
- if (!isOpening && i + 1 < lines.length && nextLine.trim().length > 0) {
16833
- issues.push({
16834
- filePath: ctx.filePath,
16835
- line: i + 1,
16836
- column: 1,
16837
- ruleId: "markdown/blanks-around-fences",
16838
- message: "Fenced code blocks should be surrounded by blank lines",
16839
- severity: "error"
16840
- });
16833
+ inFence = true;
16834
+ } else {
16835
+ const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
16836
+ if (i + 1 < lines.length && nextLine.trim().length > 0) {
16837
+ if (!/^:::/.test(nextLine.trim())) {
16838
+ issues.push({
16839
+ filePath: ctx.filePath,
16840
+ line: i + 1,
16841
+ column: 1,
16842
+ ruleId: "markdown/blanks-around-fences",
16843
+ message: "Fenced code blocks should be surrounded by blank lines",
16844
+ severity: "error"
16845
+ });
16846
+ }
16847
+ }
16848
+ inFence = false;
16841
16849
  }
16842
16850
  }
16843
16851
  }
@@ -16850,10 +16858,10 @@ var init_blanks_around_fences = __esm(() => {
16850
16858
  for (let i = 0;i < lines.length; i++) {
16851
16859
  const line = lines[i];
16852
16860
  const prevLine = i > 0 ? lines[i - 1] : "";
16853
- const isFence = /^(`{3,}|~{3,})/.test(line);
16861
+ const isFence = /^(`{3,}|~{3,})/.test(line.trim());
16854
16862
  if (isFence) {
16855
16863
  if (!inFence) {
16856
- if (i > 0 && prevLine.trim().length > 0 && result.length > 0) {
16864
+ if (i > 0 && prevLine.trim().length > 0 && !/^:::/.test(prevLine.trim()) && result.length > 0) {
16857
16865
  result.push("");
16858
16866
  }
16859
16867
  inFence = true;
@@ -16864,7 +16872,7 @@ var init_blanks_around_fences = __esm(() => {
16864
16872
  result.push(line);
16865
16873
  if (isFence && !inFence && i + 1 < lines.length) {
16866
16874
  const nextLine = lines[i + 1];
16867
- if (nextLine.trim().length > 0) {
16875
+ if (nextLine.trim().length > 0 && !/^:::/.test(nextLine.trim())) {
16868
16876
  result.push("");
16869
16877
  }
16870
16878
  }
@@ -17000,14 +17008,22 @@ var init_blanks_around_lists = __esm(() => {
17000
17008
  const issues = [];
17001
17009
  const lines = text.split(/\r?\n/);
17002
17010
  let inList = false;
17003
- let listStartLine = -1;
17011
+ let inFence = false;
17004
17012
  for (let i = 0;i < lines.length; i++) {
17005
17013
  const line = lines[i];
17006
17014
  const prevLine = i > 0 ? lines[i - 1] : "";
17015
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17016
+ inFence = !inFence;
17017
+ if (inList)
17018
+ inList = false;
17019
+ continue;
17020
+ }
17021
+ if (inFence)
17022
+ continue;
17007
17023
  const isListItem = /^(\s*)([*\-+]|\d+\.)\s+/.test(line);
17024
+ const isListContinuation = inList && !isListItem && line.trim().length > 0 && /^\s+/.test(line);
17008
17025
  if (isListItem && !inList) {
17009
17026
  inList = true;
17010
- listStartLine = i;
17011
17027
  if (i > 0 && prevLine.trim().length > 0) {
17012
17028
  issues.push({
17013
17029
  filePath: ctx.filePath,
@@ -17018,7 +17034,7 @@ var init_blanks_around_lists = __esm(() => {
17018
17034
  severity: "error"
17019
17035
  });
17020
17036
  }
17021
- } else if (!isListItem && inList && line.trim().length > 0) {
17037
+ } else if (!isListItem && !isListContinuation && inList && line.trim().length > 0) {
17022
17038
  inList = false;
17023
17039
  const prevLineIsListItem = /^(\s*)([*\-+]|\d+\.)\s+/.test(prevLine);
17024
17040
  if (prevLineIsListItem) {
@@ -17079,15 +17095,21 @@ var init_blanks_around_tables = __esm(() => {
17079
17095
  const issues = [];
17080
17096
  const lines = text.split(/\r?\n/);
17081
17097
  let inTable = false;
17082
- let tableStartLine = -1;
17098
+ let inFence = false;
17083
17099
  for (let i = 0;i < lines.length; i++) {
17084
17100
  const line = lines[i];
17085
17101
  const prevLine = i > 0 ? lines[i - 1] : "";
17086
- const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
17102
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17103
+ inFence = !inFence;
17104
+ if (inTable)
17105
+ inTable = false;
17106
+ continue;
17107
+ }
17108
+ if (inFence)
17109
+ continue;
17087
17110
  const isTableLine = /\|/.test(line) && line.trim().length > 0;
17088
17111
  if (isTableLine && !inTable) {
17089
17112
  inTable = true;
17090
- tableStartLine = i;
17091
17113
  if (i > 0 && prevLine.trim().length > 0) {
17092
17114
  issues.push({
17093
17115
  filePath: ctx.filePath,
@@ -17569,8 +17591,15 @@ var init_heading_increment = __esm(() => {
17569
17591
  const issues = [];
17570
17592
  const lines = text.split(/\r?\n/);
17571
17593
  let previousLevel = 0;
17594
+ let inFence = false;
17572
17595
  for (let i = 0;i < lines.length; i++) {
17573
17596
  const line = lines[i];
17597
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17598
+ inFence = !inFence;
17599
+ continue;
17600
+ }
17601
+ if (inFence)
17602
+ continue;
17574
17603
  const atxMatch = line.match(/^(#{1,6})\s/);
17575
17604
  if (atxMatch) {
17576
17605
  const level = atxMatch[1].length;
@@ -17602,8 +17631,15 @@ var init_heading_start_left = __esm(() => {
17602
17631
  check: (text, ctx) => {
17603
17632
  const issues = [];
17604
17633
  const lines = text.split(/\r?\n/);
17634
+ let inFence = false;
17605
17635
  for (let i = 0;i < lines.length; i++) {
17606
17636
  const line = lines[i];
17637
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17638
+ inFence = !inFence;
17639
+ continue;
17640
+ }
17641
+ if (inFence)
17642
+ continue;
17607
17643
  const match = line.match(/^(\s+)(#{1,6}\s)/);
17608
17644
  if (match) {
17609
17645
  issues.push({
@@ -17898,8 +17934,24 @@ var init_link_image_style = __esm(() => {
17898
17934
  const options = ctx.options || {};
17899
17935
  const style = options.style || "consistent";
17900
17936
  let detectedStyle = null;
17937
+ let inFence = false;
17938
+ let inHtmlComment = false;
17901
17939
  for (let i = 0;i < lines.length; i++) {
17902
17940
  const line = lines[i];
17941
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
17942
+ inFence = !inFence;
17943
+ continue;
17944
+ }
17945
+ if (inFence)
17946
+ continue;
17947
+ if (line.includes("<!--"))
17948
+ inHtmlComment = true;
17949
+ if (line.includes("-->")) {
17950
+ inHtmlComment = false;
17951
+ continue;
17952
+ }
17953
+ if (inHtmlComment)
17954
+ continue;
17903
17955
  if (line.match(/^\[([^\]]+)\]:\s*\S+/)) {
17904
17956
  continue;
17905
17957
  }
@@ -18116,19 +18168,34 @@ var init_no_bare_urls = __esm(() => {
18116
18168
  check: (text, ctx) => {
18117
18169
  const issues = [];
18118
18170
  const lines = text.split(/\r?\n/);
18171
+ let inFence = false;
18172
+ let inHtmlComment = false;
18119
18173
  for (let i = 0;i < lines.length; i++) {
18120
18174
  const line = lines[i];
18121
- if (line.trim().startsWith("```") || line.trim().startsWith("~~~")) {
18175
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18176
+ inFence = !inFence;
18177
+ continue;
18178
+ }
18179
+ if (inFence)
18180
+ continue;
18181
+ if (line.includes("<!--"))
18182
+ inHtmlComment = true;
18183
+ if (line.includes("-->")) {
18184
+ inHtmlComment = false;
18122
18185
  continue;
18123
18186
  }
18124
- const urlPattern = /(?<![<`(])https?:\/\/[^\s<>`)\]]+(?![>\])`])/g;
18125
- const matches = line.matchAll(urlPattern);
18187
+ if (inHtmlComment)
18188
+ continue;
18189
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
18190
+ continue;
18191
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
18192
+ const urlPattern = /(?<![<(="'])https?:\/\/[^\s<>`)\]"']+(?![>\])"'])/g;
18193
+ const matches = stripped.matchAll(urlPattern);
18126
18194
  for (const match of matches) {
18127
- const column = match.index + 1;
18128
18195
  issues.push({
18129
18196
  filePath: ctx.filePath,
18130
18197
  line: i + 1,
18131
- column,
18198
+ column: match.index + 1,
18132
18199
  ruleId: "markdown/no-bare-urls",
18133
18200
  message: "Bare URL used. Wrap in angle brackets: <url>",
18134
18201
  severity: "error"
@@ -18139,11 +18206,26 @@ var init_no_bare_urls = __esm(() => {
18139
18206
  },
18140
18207
  fix: (text) => {
18141
18208
  const lines = text.split(/\r?\n/);
18209
+ let inFence = false;
18210
+ let inHtmlComment = false;
18142
18211
  const fixedLines = lines.map((line) => {
18143
- if (line.trim().startsWith("```") || line.trim().startsWith("~~~")) {
18212
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18213
+ inFence = !inFence;
18144
18214
  return line;
18145
18215
  }
18146
- return line.replace(/(?<![<`(])https?:\/\/[^\s<>`)\]]+(?![>\])`])/g, "<$&>");
18216
+ if (inFence)
18217
+ return line;
18218
+ if (line.includes("<!--"))
18219
+ inHtmlComment = true;
18220
+ if (line.includes("-->")) {
18221
+ inHtmlComment = false;
18222
+ return line;
18223
+ }
18224
+ if (inHtmlComment)
18225
+ return line;
18226
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
18227
+ return line;
18228
+ return line.replace(/(?<![<(="'])https?:\/\/[^\s<>`)\]"']+(?![>\])"'])/g, "<$&>");
18147
18229
  });
18148
18230
  return fixedLines.join(`
18149
18231
  `);
@@ -18199,8 +18281,15 @@ var init_no_duplicate_heading = __esm(() => {
18199
18281
  const issues = [];
18200
18282
  const lines = text.split(/\r?\n/);
18201
18283
  const headings = new Map;
18284
+ let inFence = false;
18202
18285
  for (let i = 0;i < lines.length; i++) {
18203
18286
  const line = lines[i];
18287
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18288
+ inFence = !inFence;
18289
+ continue;
18290
+ }
18291
+ if (inFence)
18292
+ continue;
18204
18293
  const atxMatch = line.match(/^#{1,6}\s+(.+?)(?:\s*#+\s*)?$/);
18205
18294
  if (atxMatch) {
18206
18295
  const content = atxMatch[1].trim();
@@ -18249,10 +18338,17 @@ var init_no_emphasis_as_heading = __esm(() => {
18249
18338
  check: (text, ctx) => {
18250
18339
  const issues = [];
18251
18340
  const lines = text.split(/\r?\n/);
18341
+ let inFence = false;
18252
18342
  for (let i = 0;i < lines.length; i++) {
18253
18343
  const line = lines[i];
18254
18344
  const prevLine = i > 0 ? lines[i - 1] : "";
18255
18345
  const nextLine = i + 1 < lines.length ? lines[i + 1] : "";
18346
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18347
+ inFence = !inFence;
18348
+ continue;
18349
+ }
18350
+ if (inFence)
18351
+ continue;
18256
18352
  const isBoldLine = /^\*\*[^*]+\*\*\s*$/.test(line) || /^__[^_]+__\s*$/.test(line);
18257
18353
  const isItalicLine = /^\*[^*]+\*\s*$/.test(line) || /^_[^_]+_\s*$/.test(line);
18258
18354
  const isStandalone = prevLine.trim().length === 0 && nextLine.trim().length === 0;
@@ -18358,9 +18454,17 @@ var init_no_inline_html = __esm(() => {
18358
18454
  const lines = text.split(/\r?\n/);
18359
18455
  const options = ctx.options || {};
18360
18456
  const allowedElements = options.allowed_elements || [];
18457
+ let inFence = false;
18361
18458
  for (let i = 0;i < lines.length; i++) {
18362
18459
  const line = lines[i];
18363
- const matches = line.matchAll(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi);
18460
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18461
+ inFence = !inFence;
18462
+ continue;
18463
+ }
18464
+ if (inFence)
18465
+ continue;
18466
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
18467
+ const matches = stripped.matchAll(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi);
18364
18468
  for (const match of matches) {
18365
18469
  const tagName = match[1].toLowerCase();
18366
18470
  if (!allowedElements.includes(tagName)) {
@@ -18676,27 +18780,54 @@ var init_no_space_in_code = __esm(() => {
18676
18780
  check: (text, ctx) => {
18677
18781
  const issues = [];
18678
18782
  const lines = text.split(/\r?\n/);
18783
+ let inFence = false;
18679
18784
  for (let i = 0;i < lines.length; i++) {
18680
18785
  const line = lines[i];
18681
- const matches = line.matchAll(/(`+)\s+([^`]+?)\s+\1/g);
18682
- for (const match of matches) {
18683
- const column = match.index + 1;
18684
- issues.push({
18685
- filePath: ctx.filePath,
18686
- line: i + 1,
18687
- column,
18688
- ruleId: "markdown/no-space-in-code",
18689
- message: "Spaces inside code span elements",
18690
- severity: "error"
18691
- });
18786
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18787
+ inFence = !inFence;
18788
+ continue;
18789
+ }
18790
+ if (inFence)
18791
+ continue;
18792
+ const codeSpanPattern = /(`+)([\s\S]*?)\1/g;
18793
+ for (const match of line.matchAll(codeSpanPattern)) {
18794
+ const content = match[2];
18795
+ if (content.startsWith(" ") && content.endsWith(" ") && content.trim().length > 0) {
18796
+ issues.push({
18797
+ filePath: ctx.filePath,
18798
+ line: i + 1,
18799
+ column: match.index + 1,
18800
+ ruleId: "markdown/no-space-in-code",
18801
+ message: "Spaces inside code span elements",
18802
+ severity: "error"
18803
+ });
18804
+ }
18692
18805
  }
18693
18806
  }
18694
18807
  return issues;
18695
18808
  },
18696
18809
  fix: (text) => {
18697
- return text.replace(/(`+)\s+([^`]+?)\s+\1/g, (match, backticks, content) => {
18698
- return `${backticks}${content.trim()}${backticks}`;
18699
- });
18810
+ const lines = text.split(/\r?\n/);
18811
+ let inFence = false;
18812
+ const result = [];
18813
+ for (const line of lines) {
18814
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18815
+ inFence = !inFence;
18816
+ result.push(line);
18817
+ continue;
18818
+ }
18819
+ if (inFence) {
18820
+ result.push(line);
18821
+ continue;
18822
+ }
18823
+ result.push(line.replace(/(`+)([\s\S]*?)\1/g, (match, backticks, content) => {
18824
+ if (content.startsWith(" ") && content.endsWith(" ") && content.trim().length > 0)
18825
+ return `${backticks}${content.trim()}${backticks}`;
18826
+ return match;
18827
+ }));
18828
+ }
18829
+ return result.join(`
18830
+ `);
18700
18831
  }
18701
18832
  };
18702
18833
  });
@@ -18711,20 +18842,28 @@ var init_no_space_in_emphasis = __esm(() => {
18711
18842
  check: (text, ctx) => {
18712
18843
  const issues = [];
18713
18844
  const lines = text.split(/\r?\n/);
18845
+ let inFence = false;
18714
18846
  for (let i = 0;i < lines.length; i++) {
18715
18847
  const line = lines[i];
18848
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18849
+ inFence = !inFence;
18850
+ continue;
18851
+ }
18852
+ if (inFence)
18853
+ continue;
18854
+ const stripped = line.replace(/`[^`]+`/g, (m) => "\x01".repeat(m.length));
18716
18855
  const patterns = [
18717
- /(\*\*|\*|__?)\s+/g,
18718
- /\s+(\*\*|\*|__?)/g
18856
+ /\*\*\s+[^*]+?\*\*/g,
18857
+ /\*\*[^*]+?\s+\*\*/g,
18858
+ /__\s+[^_]+?__/g,
18859
+ /__[^_]+?\s+__/g
18719
18860
  ];
18720
18861
  for (const pattern of patterns) {
18721
- const matches = line.matchAll(pattern);
18722
- for (const match of matches) {
18723
- const column = match.index + 1;
18862
+ for (const match of stripped.matchAll(pattern)) {
18724
18863
  issues.push({
18725
18864
  filePath: ctx.filePath,
18726
18865
  line: i + 1,
18727
- column,
18866
+ column: match.index + 1,
18728
18867
  ruleId: "markdown/no-space-in-emphasis",
18729
18868
  message: "Spaces inside emphasis markers",
18730
18869
  severity: "error"
@@ -18735,9 +18874,28 @@ var init_no_space_in_emphasis = __esm(() => {
18735
18874
  return issues;
18736
18875
  },
18737
18876
  fix: (text) => {
18738
- let fixed = text.replace(/(\*\*|\*|__?)\s+/g, "$1");
18739
- fixed = fixed.replace(/\s+(\*\*|\*|__?)/g, "$1");
18740
- return fixed;
18877
+ const lines = text.split(/\r?\n/);
18878
+ let inFence = false;
18879
+ const result = [];
18880
+ for (const line of lines) {
18881
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
18882
+ inFence = !inFence;
18883
+ result.push(line);
18884
+ continue;
18885
+ }
18886
+ if (inFence) {
18887
+ result.push(line);
18888
+ continue;
18889
+ }
18890
+ let fixed = line;
18891
+ fixed = fixed.replace(/\*\*\s+([^*]+?)\*\*/g, "**$1**");
18892
+ fixed = fixed.replace(/\*\*([^*]+?)\s+\*\*/g, "**$1**");
18893
+ fixed = fixed.replace(/__\s+([^_]+?)__/g, "__$1__");
18894
+ fixed = fixed.replace(/__([^_]+?)\s+__/g, "__$1__");
18895
+ result.push(fixed);
18896
+ }
18897
+ return result.join(`
18898
+ `);
18741
18899
  }
18742
18900
  };
18743
18901
  });
@@ -19041,20 +19199,37 @@ var init_reference_links_images = __esm(() => {
19041
19199
  definitions.add(defMatch[1].toLowerCase());
19042
19200
  }
19043
19201
  }
19202
+ let inFence = false;
19203
+ let inHtmlComment = false;
19044
19204
  for (let i = 0;i < lines.length; i++) {
19045
19205
  const line = lines[i];
19046
- const linkMatches = line.matchAll(/\[([^\]]+)\](?:\[([^\]]+)\])?(?!\()/g);
19206
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19207
+ inFence = !inFence;
19208
+ continue;
19209
+ }
19210
+ if (inFence)
19211
+ continue;
19212
+ if (line.includes("<!--"))
19213
+ inHtmlComment = true;
19214
+ if (line.includes("-->")) {
19215
+ inHtmlComment = false;
19216
+ continue;
19217
+ }
19218
+ if (inHtmlComment)
19219
+ continue;
19220
+ if (/^\[([^\]]+)\]:\s*\S+/.test(line))
19221
+ continue;
19222
+ const stripped = line.replace(/`[^`]+`/g, (m) => " ".repeat(m.length));
19223
+ const linkMatches = stripped.matchAll(/\[([^\]]+)\](?:\[([^\]]+)\])?(?!\()/g);
19047
19224
  for (const match of linkMatches) {
19048
19225
  const label = (match[2] || match[1]).toLowerCase();
19049
- if (line.match(/^\[([^\]]+)\]:\s*\S+/)) {
19226
+ if (/^[xX ]$/.test(label))
19050
19227
  continue;
19051
- }
19052
19228
  if (!definitions.has(label)) {
19053
- const column = match.index + 1;
19054
19229
  issues.push({
19055
19230
  filePath: ctx.filePath,
19056
19231
  line: i + 1,
19057
- column,
19232
+ column: match.index + 1,
19058
19233
  ruleId: "markdown/reference-links-images",
19059
19234
  message: `Reference link '[${label}]' is not defined`,
19060
19235
  severity: "error"
@@ -19397,8 +19572,15 @@ var init_table_pipe_style = __esm(() => {
19397
19572
  const lines = text.split(/\r?\n/);
19398
19573
  const options = ctx.options || {};
19399
19574
  const style = options.style || "leading_and_trailing";
19575
+ let inFence = false;
19400
19576
  for (let i = 0;i < lines.length; i++) {
19401
19577
  const line = lines[i];
19578
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19579
+ inFence = !inFence;
19580
+ continue;
19581
+ }
19582
+ if (inFence)
19583
+ continue;
19402
19584
  if (/\|/.test(line) && line.trim().length > 0) {
19403
19585
  const hasLeading = line.trim().startsWith("|");
19404
19586
  const hasTrailing = line.trim().endsWith("|");
@@ -19466,8 +19648,15 @@ var init_ul_indent = __esm(() => {
19466
19648
  const lines = text.split(/\r?\n/);
19467
19649
  const options = ctx.options || {};
19468
19650
  const expectedIndent = options.indent || 2;
19651
+ let inFence = false;
19469
19652
  for (let i = 0;i < lines.length; i++) {
19470
19653
  const line = lines[i];
19654
+ if (/^(`{3,}|~{3,})/.test(line.trim())) {
19655
+ inFence = !inFence;
19656
+ continue;
19657
+ }
19658
+ if (inFence)
19659
+ continue;
19471
19660
  const match = line.match(/^(\s*)([*\-+])\s+/);
19472
19661
  if (match) {
19473
19662
  const indent = match[1].length;
@@ -19581,18 +19770,49 @@ var init_ul_style = __esm(() => {
19581
19770
  });
19582
19771
 
19583
19772
  // src/plugins/markdown.ts
19773
+ function stripFrontmatter(content) {
19774
+ const lines = content.split(/\r?\n/);
19775
+ if (lines[0]?.trim() !== "---")
19776
+ return { text: content, frontmatter: null, frontmatterEndLine: 0 };
19777
+ for (let i = 1;i < lines.length; i++) {
19778
+ if (lines[i].trim() === "---") {
19779
+ const frontmatter = lines.slice(0, i + 1);
19780
+ const blanked = frontmatter.map(() => "");
19781
+ return {
19782
+ text: [...blanked, ...lines.slice(i + 1)].join(`
19783
+ `),
19784
+ frontmatter,
19785
+ frontmatterEndLine: i + 1
19786
+ };
19787
+ }
19788
+ }
19789
+ return { text: content, frontmatter: null, frontmatterEndLine: 0 };
19790
+ }
19791
+ function restoreFrontmatter(content, frontmatter) {
19792
+ if (!frontmatter)
19793
+ return content;
19794
+ const lines = content.split(/\r?\n/);
19795
+ return [...frontmatter, ...lines.slice(frontmatter.length)].join(`
19796
+ `);
19797
+ }
19584
19798
  function markdownOnly(rule) {
19585
19799
  return {
19586
19800
  meta: rule.meta,
19587
19801
  check: (content, context) => {
19588
19802
  if (!context.filePath.endsWith(".md"))
19589
19803
  return [];
19590
- return rule.check(content, context);
19804
+ const { text, frontmatterEndLine } = stripFrontmatter(content);
19805
+ const issues = rule.check(text, context);
19806
+ if (frontmatterEndLine > 0)
19807
+ return issues.filter((issue) => issue.line > frontmatterEndLine + 1);
19808
+ return issues;
19591
19809
  },
19592
19810
  fix: rule.fix ? (content, context) => {
19593
19811
  if (!context.filePath.endsWith(".md"))
19594
19812
  return content;
19595
- return rule.fix(content, context);
19813
+ const { text, frontmatter } = stripFrontmatter(content);
19814
+ const fixed = rule.fix(text, context);
19815
+ return restoreFrontmatter(fixed, frontmatter);
19596
19816
  } : undefined
19597
19817
  };
19598
19818
  }
@@ -25281,13 +25501,13 @@ var init_style = __esm(() => {
25281
25501
  stylePlugin = {
25282
25502
  name: "style",
25283
25503
  rules: {
25284
- "brace-style": braceStyle,
25285
- curly: curlyRule,
25286
- "max-statements-per-line": maxStatementsPerLineRule,
25287
- "if-newline": ifNewlineRule,
25288
- "consistent-chaining": consistentChainingRule,
25289
- "consistent-list-newline": consistentListNewlineRule,
25290
- "indent-unindent": indentUnindentRule,
25504
+ "brace-style": codeOnly(braceStyle),
25505
+ curly: codeOnly(curlyRule),
25506
+ "max-statements-per-line": codeOnly(maxStatementsPerLineRule),
25507
+ "if-newline": codeOnly(ifNewlineRule),
25508
+ "consistent-chaining": codeOnly(consistentChainingRule),
25509
+ "consistent-list-newline": codeOnly(consistentListNewlineRule),
25510
+ "indent-unindent": codeOnly(indentUnindentRule),
25291
25511
  "no-multi-spaces": noMultiSpaces,
25292
25512
  "no-multiple-empty-lines": noMultipleEmptyLines,
25293
25513
  "no-trailing-spaces": noTrailingSpaces,
@@ -28051,6 +28271,18 @@ function scanContentOptimized(filePath, content, cfg, suppress, commentLines) {
28051
28271
  const wantNoCondAssign = sevMap(cfg.rules.noCondAssign);
28052
28272
  const consoleCall = /\bconsole\.log\s*\(/;
28053
28273
  const debuggerStmt = /^\s*debugger\b/;
28274
+ const linesInFencedCodeBlock = new Set;
28275
+ if (fileExt === "md") {
28276
+ let inFence = false;
28277
+ for (let li = 0;li < lines.length; li++) {
28278
+ if (/^(`{3,}|~{3,})/.test(lines[li].trim())) {
28279
+ inFence = !inFence;
28280
+ continue;
28281
+ }
28282
+ if (inFence)
28283
+ linesInFencedCodeBlock.add(li + 1);
28284
+ }
28285
+ }
28054
28286
  const linesInTemplate = new Set;
28055
28287
  let inTemplate = false;
28056
28288
  let escaped = false;
@@ -28099,7 +28331,7 @@ function scanContentOptimized(filePath, content, cfg, suppress, commentLines) {
28099
28331
  }
28100
28332
  const leadingMatch = line.match(/^[ \t]*/);
28101
28333
  const leading = leadingMatch ? leadingMatch[0] : "";
28102
- if (leading.length > 0 && hasIndentIssue(leading, cfg.format.indent, cfg.format.indentStyle)) {
28334
+ if (leading.length > 0 && !linesInFencedCodeBlock.has(lineNo) && hasIndentIssue(leading, cfg.format.indent, cfg.format.indentStyle, line)) {
28103
28335
  if (!isSuppressed("indent", lineNo, suppress))
28104
28336
  issues.push({ filePath, line: lineNo, column: 1, ruleId: "indent", message: "Incorrect indentation detected", severity: "warning", help: `Use ${cfg.format.indentStyle === "spaces" ? `${cfg.format.indent} spaces` : "tabs"} for indentation. Configure with format.indent and format.indentStyle in your config` });
28105
28337
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pickier",
3
3
  "type": "module",
4
- "version": "0.1.15",
4
+ "version": "0.1.16",
5
5
  "description": "Format, lint and more in a fraction of seconds.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",