eslint-plugin-markdown-preferences 0.3.3 → 0.4.0

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/README.md CHANGED
@@ -93,6 +93,7 @@ The rules with the following star ⭐ are included in the configs.
93
93
  | [markdown-preferences/hard-linebreak-style](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/hard-linebreak-style.html) | enforce consistent hard linebreak style. | 🔧 | ⭐ |
94
94
  | [markdown-preferences/no-text-backslash-linebreak](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-text-backslash-linebreak.html) | disallow text backslash at the end of a line. | | ⭐ |
95
95
  | [markdown-preferences/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-trailing-spaces.html) | trailing whitespace at the end of lines in Markdown files. | 🔧 | |
96
+ | [markdown-preferences/prefer-inline-code-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-inline-code-words.html) | enforce the use of inline code for specific words. | 🔧 | |
96
97
  | [markdown-preferences/prefer-linked-words](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/prefer-linked-words.html) | enforce the specified word to be a link. | 🔧 | |
97
98
 
98
99
  <!--RULES_TABLE_END-->
package/lib/index.d.ts CHANGED
@@ -19,7 +19,7 @@ declare namespace meta_d_exports {
19
19
  export { name, version };
20
20
  }
21
21
  declare const name: "eslint-plugin-markdown-preferences";
22
- declare const version: "0.3.3";
22
+ declare const version: "0.4.0";
23
23
  //#endregion
24
24
  //#region src/index.d.ts
25
25
  declare const configs: {
package/lib/index.js CHANGED
@@ -244,8 +244,99 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
244
244
  });
245
245
 
246
246
  //#endregion
247
- //#region src/rules/prefer-linked-words.ts
247
+ //#region src/utils/search-words.ts
248
248
  const RE_BOUNDARY = /^[\s\p{Letter_Number}\p{Modifier_Letter}\p{Modifier_Symbol}\p{Nonspacing_Mark}\p{Other_Letter}\p{Other_Symbol}\p{Script=Han}!"#$%&'(),./:;<=>?\\{|}~\u{2ffc}-\u{303d}\u{30a0}-\u{30fb}\u{3192}-\u{32bf}\u{fe10}-\u{fe1f}\u{fe30}-\u{fe6f}\u{ff00}-\u{ffef}\u{2ebf0}-\u{2ee5d}]*$/u;
249
+ /**
250
+ * Iterate through words in a text node that match the specified words.
251
+ */
252
+ function* iterateSearchWords(sourceCode, node, words) {
253
+ const text = sourceCode.getText(node);
254
+ for (const word of words) {
255
+ let startPosition = 0;
256
+ while (true) {
257
+ const index = text.indexOf(word, startPosition);
258
+ if (index < 0) break;
259
+ startPosition = index + word.length;
260
+ if (!RE_BOUNDARY.test(text[index - 1] || "") || !RE_BOUNDARY.test(text[index + word.length] || "")) continue;
261
+ const loc = sourceCode.getLoc(node);
262
+ const beforeLines = text.slice(0, index).split(/\n/u);
263
+ const line = loc.start.line + beforeLines.length - 1;
264
+ const column = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
265
+ const range = sourceCode.getRange(node);
266
+ yield {
267
+ loc: {
268
+ start: {
269
+ line,
270
+ column
271
+ },
272
+ end: {
273
+ line,
274
+ column: column + word.length
275
+ }
276
+ },
277
+ range: [range[0] + index, range[0] + index + word.length],
278
+ word
279
+ };
280
+ }
281
+ }
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/rules/prefer-inline-code-words.ts
286
+ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
287
+ meta: {
288
+ type: "suggestion",
289
+ docs: {
290
+ description: "enforce the use of inline code for specific words.",
291
+ categories: []
292
+ },
293
+ fixable: "code",
294
+ hasSuggestions: false,
295
+ schema: [{
296
+ type: "object",
297
+ properties: { words: {
298
+ type: "array",
299
+ items: { type: "string" }
300
+ } },
301
+ required: ["words"]
302
+ }],
303
+ messages: { requireInlineCode: "The word \"{{name}}\" should be in inline code." }
304
+ },
305
+ create(context) {
306
+ const sourceCode = context.sourceCode;
307
+ const words = context.options[0]?.words || [];
308
+ let shortcutLinkReference = null;
309
+ return {
310
+ linkReference(node) {
311
+ if (node.referenceType !== "shortcut") return;
312
+ if (shortcutLinkReference) return;
313
+ shortcutLinkReference = node;
314
+ },
315
+ "linkReference:exit"(node) {
316
+ if (shortcutLinkReference === node) shortcutLinkReference = null;
317
+ },
318
+ text(node) {
319
+ for (const { word, loc, range } of iterateSearchWords(sourceCode, node, words)) {
320
+ const shortcutLinkReferenceToReport = shortcutLinkReference;
321
+ context.report({
322
+ node,
323
+ loc,
324
+ messageId: "requireInlineCode",
325
+ data: { name: word },
326
+ *fix(fixer) {
327
+ yield fixer.insertTextBeforeRange(range, "`");
328
+ yield fixer.insertTextAfterRange(range, "`");
329
+ if (shortcutLinkReferenceToReport) yield fixer.insertTextAfter(shortcutLinkReferenceToReport, `[${shortcutLinkReferenceToReport.label}]`);
330
+ }
331
+ });
332
+ }
333
+ }
334
+ };
335
+ }
336
+ });
337
+
338
+ //#endregion
339
+ //#region src/rules/prefer-linked-words.ts
249
340
  var prefer_linked_words_default = createRule("prefer-linked-words", {
250
341
  meta: {
251
342
  type: "suggestion",
@@ -270,8 +361,18 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
270
361
  },
271
362
  create(context) {
272
363
  const sourceCode = context.sourceCode;
273
- const words = context.options[0]?.words || {};
274
- const wordEntries = (Array.isArray(words) ? words.map((word) => [word, void 0]) : Object.entries(words)).map(([word, link]) => [word, link ? adjustLink(link) : void 0]).filter(([, link]) => link !== `./${path.basename(context.filename)}`);
364
+ const wordsOption = context.options[0]?.words || {};
365
+ const links = Object.create(null);
366
+ const words = [];
367
+ if (Array.isArray(wordsOption)) words.push(...wordsOption);
368
+ else for (const [word, link] of Object.entries(wordsOption)) {
369
+ if (link) {
370
+ const adjustedLink = adjustLink(link);
371
+ if (adjustedLink === `./${path.basename(context.filename)}`) continue;
372
+ links[word] = adjustedLink;
373
+ }
374
+ words.push(word);
375
+ }
275
376
  let ignore = null;
276
377
  return {
277
378
  "link, linkReference, heading, footnoteDefinition"(node) {
@@ -283,50 +384,32 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
283
384
  },
284
385
  text(node) {
285
386
  if (ignore) return;
286
- const text = sourceCode.getText(node);
287
- for (const [word, link] of wordEntries) {
288
- let startPosition = 0;
289
- while (true) {
290
- const index = text.indexOf(word, startPosition);
291
- if (index < 0) break;
292
- startPosition = index + word.length;
293
- if (!RE_BOUNDARY.test(text[index - 1] || "") || !RE_BOUNDARY.test(text[index + word.length] || "")) continue;
294
- const loc = sourceCode.getLoc(node);
295
- const beforeLines = text.slice(0, index).split(/\n/u);
296
- const line = loc.start.line + beforeLines.length - 1;
297
- const column = (beforeLines.length === 1 ? loc.start.column : 1) + (beforeLines.at(-1) || "").length;
298
- context.report({
299
- node,
300
- loc: {
301
- start: {
302
- line,
303
- column
304
- },
305
- end: {
306
- line,
307
- column: column + word.length
308
- }
309
- },
310
- messageId: "requireLink",
311
- data: { name: word },
312
- fix: link ? (fixer) => {
313
- const [start] = sourceCode.getRange(node);
314
- return fixer.replaceTextRange([start + index, start + index + word.length], `[${word}](${link})`);
315
- } : null
316
- });
317
- }
387
+ for (const { word, loc, range } of iterateSearchWords(sourceCode, node, words)) {
388
+ const link = links[word];
389
+ context.report({
390
+ node,
391
+ loc,
392
+ messageId: "requireLink",
393
+ data: { name: word },
394
+ fix: link ? (fixer) => {
395
+ return fixer.replaceTextRange(range, `[${word}](${link})`);
396
+ } : null
397
+ });
318
398
  }
319
399
  },
320
400
  inlineCode(node) {
321
401
  if (ignore) return;
322
- for (const [word, link] of wordEntries) if (node.value === word) context.report({
323
- node,
324
- messageId: "requireLink",
325
- data: { name: word },
326
- fix: link ? (fixer) => {
327
- return fixer.replaceText(node, `[\`${word}\`](${link})`);
328
- } : null
329
- });
402
+ for (const word of words) if (node.value === word) {
403
+ const link = links[word];
404
+ context.report({
405
+ node,
406
+ messageId: "requireLink",
407
+ data: { name: word },
408
+ fix: link ? (fixer) => {
409
+ return fixer.replaceText(node, `[\`${word}\`](${link})`);
410
+ } : null
411
+ });
412
+ }
330
413
  }
331
414
  };
332
415
  /**
@@ -347,6 +430,7 @@ const rules$1 = [
347
430
  hard_linebreak_style_default,
348
431
  no_text_backslash_linebreak_default,
349
432
  no_trailing_spaces_default,
433
+ prefer_inline_code_words_default,
350
434
  prefer_linked_words_default
351
435
  ];
352
436
 
@@ -382,7 +466,7 @@ __export(meta_exports, {
382
466
  version: () => version
383
467
  });
384
468
  const name = "eslint-plugin-markdown-preferences";
385
- const version = "0.3.3";
469
+ const version = "0.4.0";
386
470
 
387
471
  //#endregion
388
472
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {