eslint-plugin-markdown-preferences 0.7.0 → 0.9.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 +17 -9
- package/lib/index.d.ts +43 -2
- package/lib/index.js +1253 -38
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -25,6 +25,98 @@ function createRule(ruleName, rule) {
|
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/rules/canonical-code-block-language.ts
|
|
30
|
+
const DEFAULT_LANGUAGES = {
|
|
31
|
+
javascript: "js",
|
|
32
|
+
jsx: "js",
|
|
33
|
+
mjs: "js",
|
|
34
|
+
cjs: "js",
|
|
35
|
+
typescript: "ts",
|
|
36
|
+
tsx: "ts",
|
|
37
|
+
mts: "ts",
|
|
38
|
+
cts: "ts",
|
|
39
|
+
python: "py",
|
|
40
|
+
bash: "sh",
|
|
41
|
+
shell: "sh",
|
|
42
|
+
zsh: "sh",
|
|
43
|
+
yml: "yaml",
|
|
44
|
+
markdown: "md",
|
|
45
|
+
rust: "rs",
|
|
46
|
+
golang: "go",
|
|
47
|
+
cplusplus: "cpp",
|
|
48
|
+
"c++": "cpp",
|
|
49
|
+
postgresql: "sql",
|
|
50
|
+
mysql: "sql",
|
|
51
|
+
sqlite: "sql"
|
|
52
|
+
};
|
|
53
|
+
var canonical_code_block_language_default = createRule("canonical-code-block-language", {
|
|
54
|
+
meta: {
|
|
55
|
+
type: "suggestion",
|
|
56
|
+
docs: {
|
|
57
|
+
description: "enforce canonical language names in code blocks",
|
|
58
|
+
categories: [],
|
|
59
|
+
listCategory: "Stylistic"
|
|
60
|
+
},
|
|
61
|
+
fixable: "code",
|
|
62
|
+
hasSuggestions: false,
|
|
63
|
+
schema: [{
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: { languages: {
|
|
66
|
+
type: "object",
|
|
67
|
+
patternProperties: { "^[\\s\\S]+$": { type: "string" } },
|
|
68
|
+
additionalProperties: false
|
|
69
|
+
} },
|
|
70
|
+
additionalProperties: false
|
|
71
|
+
}],
|
|
72
|
+
messages: { useCanonical: "Use canonical language name \"{{canonical}}\" instead of \"{{current}}\"." }
|
|
73
|
+
},
|
|
74
|
+
create(context) {
|
|
75
|
+
const sourceCode = context.sourceCode;
|
|
76
|
+
const languages = context.options[0]?.languages || DEFAULT_LANGUAGES;
|
|
77
|
+
return { code(node) {
|
|
78
|
+
if (!node.lang || !languages[node.lang]) return;
|
|
79
|
+
const canonical = languages[node.lang];
|
|
80
|
+
const current = node.lang;
|
|
81
|
+
if (current === canonical) return;
|
|
82
|
+
const nodeRange = sourceCode.getRange(node);
|
|
83
|
+
const nodeText = sourceCode.text.slice(nodeRange[0], nodeRange[1]);
|
|
84
|
+
const fenceRegex = /^(`{3,}|~{3,})(\w*)(?:\s.*)?$/mu;
|
|
85
|
+
const fenceMatch = fenceRegex.exec(nodeText.split("\n")[0]);
|
|
86
|
+
if (!fenceMatch) return;
|
|
87
|
+
const [, fence, langInfo] = fenceMatch;
|
|
88
|
+
const langStart = nodeRange[0] + fence.length;
|
|
89
|
+
const langEnd = langStart + langInfo.length;
|
|
90
|
+
const nodeLoc = sourceCode.getLoc(node);
|
|
91
|
+
const beforeFence = sourceCode.text.slice(nodeRange[0], langStart);
|
|
92
|
+
const beforeLines = beforeFence.split(/\n/u);
|
|
93
|
+
const line = nodeLoc.start.line + beforeLines.length - 1;
|
|
94
|
+
const column = (beforeLines.length === 1 ? nodeLoc.start.column : 0) + (beforeLines.at(-1) || "").length;
|
|
95
|
+
context.report({
|
|
96
|
+
node,
|
|
97
|
+
loc: {
|
|
98
|
+
start: {
|
|
99
|
+
line,
|
|
100
|
+
column
|
|
101
|
+
},
|
|
102
|
+
end: {
|
|
103
|
+
line,
|
|
104
|
+
column: column + langInfo.length
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
messageId: "useCanonical",
|
|
108
|
+
data: {
|
|
109
|
+
canonical,
|
|
110
|
+
current
|
|
111
|
+
},
|
|
112
|
+
fix(fixer) {
|
|
113
|
+
return fixer.replaceTextRange([langStart, langEnd], canonical);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
} };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
28
120
|
//#endregion
|
|
29
121
|
//#region src/rules/definitions-last.ts
|
|
30
122
|
var definitions_last_default = createRule("definitions-last", {
|
|
@@ -32,7 +124,8 @@ var definitions_last_default = createRule("definitions-last", {
|
|
|
32
124
|
type: "layout",
|
|
33
125
|
docs: {
|
|
34
126
|
description: "require link definitions and footnote definitions to be placed at the end of the document",
|
|
35
|
-
categories: []
|
|
127
|
+
categories: [],
|
|
128
|
+
listCategory: "Stylistic"
|
|
36
129
|
},
|
|
37
130
|
fixable: "code",
|
|
38
131
|
hasSuggestions: false,
|
|
@@ -72,7 +165,8 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
|
|
|
72
165
|
type: "layout",
|
|
73
166
|
docs: {
|
|
74
167
|
description: "enforce consistent hard linebreak style.",
|
|
75
|
-
categories: ["recommended"]
|
|
168
|
+
categories: ["recommended"],
|
|
169
|
+
listCategory: "Stylistic"
|
|
76
170
|
},
|
|
77
171
|
fixable: "code",
|
|
78
172
|
hasSuggestions: false,
|
|
@@ -109,6 +203,781 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
|
|
|
109
203
|
}
|
|
110
204
|
});
|
|
111
205
|
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/utils/regexp.ts
|
|
208
|
+
const RE_REGEXP_STR = /^\/(.+)\/([A-Za-z]*)$/u;
|
|
209
|
+
/**
|
|
210
|
+
* Convert a string to the `RegExp`.
|
|
211
|
+
* Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`.
|
|
212
|
+
* Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} string The string to convert.
|
|
215
|
+
* @returns {RegExp} Returns the `RegExp`.
|
|
216
|
+
*/
|
|
217
|
+
function toRegExp(string) {
|
|
218
|
+
const parts = RE_REGEXP_STR.exec(string);
|
|
219
|
+
if (parts) return new RegExp(parts[1], parts[2]);
|
|
220
|
+
return { test: (s) => s === string };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Checks whether given string is regexp string
|
|
224
|
+
* @param {string} string
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
function isRegExp(string) {
|
|
228
|
+
return Boolean(RE_REGEXP_STR.test(string));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/resources/preserve-words.ts
|
|
233
|
+
const defaultPreserveWords = [
|
|
234
|
+
"JavaScript",
|
|
235
|
+
"TypeScript",
|
|
236
|
+
"ECMAScript",
|
|
237
|
+
"ES6",
|
|
238
|
+
...(function* () {
|
|
239
|
+
const end = (/* @__PURE__ */ new Date()).getFullYear() + 3;
|
|
240
|
+
for (let i = 2015; i <= end; i++) yield `ES${i}`;
|
|
241
|
+
})(),
|
|
242
|
+
"PHP",
|
|
243
|
+
"Python",
|
|
244
|
+
"Java",
|
|
245
|
+
"C#",
|
|
246
|
+
"C++",
|
|
247
|
+
"Rust",
|
|
248
|
+
"Go",
|
|
249
|
+
"go",
|
|
250
|
+
"Swift",
|
|
251
|
+
"Kotlin",
|
|
252
|
+
"Dart",
|
|
253
|
+
"Ruby",
|
|
254
|
+
"Scala",
|
|
255
|
+
"Perl",
|
|
256
|
+
"R",
|
|
257
|
+
"MATLAB",
|
|
258
|
+
"Lua",
|
|
259
|
+
"Haskell",
|
|
260
|
+
"Elixir",
|
|
261
|
+
"Clojure",
|
|
262
|
+
"F#",
|
|
263
|
+
"OCaml",
|
|
264
|
+
"Zig",
|
|
265
|
+
"V",
|
|
266
|
+
"Nim",
|
|
267
|
+
"Crystal",
|
|
268
|
+
"Gleam",
|
|
269
|
+
"Odin",
|
|
270
|
+
"Carbon",
|
|
271
|
+
"HTML",
|
|
272
|
+
"CSS",
|
|
273
|
+
"Sass",
|
|
274
|
+
"SCSS",
|
|
275
|
+
"Less",
|
|
276
|
+
"Stylus",
|
|
277
|
+
"JSON",
|
|
278
|
+
"XML",
|
|
279
|
+
"PDF",
|
|
280
|
+
"CSV",
|
|
281
|
+
"YAML",
|
|
282
|
+
"TOML",
|
|
283
|
+
"Markdown",
|
|
284
|
+
"LaTeX",
|
|
285
|
+
"Parquet",
|
|
286
|
+
"Avro",
|
|
287
|
+
"Protobuf",
|
|
288
|
+
"MessagePack",
|
|
289
|
+
"BSON",
|
|
290
|
+
"HDF5",
|
|
291
|
+
"Apache Arrow",
|
|
292
|
+
"ORC",
|
|
293
|
+
"SQL",
|
|
294
|
+
"GraphQL",
|
|
295
|
+
"React",
|
|
296
|
+
"Vue",
|
|
297
|
+
"Angular",
|
|
298
|
+
"Redux",
|
|
299
|
+
"Svelte",
|
|
300
|
+
"SvelteKit",
|
|
301
|
+
"Preact",
|
|
302
|
+
"Solid",
|
|
303
|
+
"Alpine.js",
|
|
304
|
+
"Lit",
|
|
305
|
+
"Stencil",
|
|
306
|
+
"Ember.js",
|
|
307
|
+
"Backbone.js",
|
|
308
|
+
"jQuery",
|
|
309
|
+
"D3.js",
|
|
310
|
+
"Three.js",
|
|
311
|
+
"Chart.js",
|
|
312
|
+
"Plotly",
|
|
313
|
+
"Astro",
|
|
314
|
+
"Remix",
|
|
315
|
+
"Qwik",
|
|
316
|
+
"SolidJS",
|
|
317
|
+
"Vike",
|
|
318
|
+
"Node.js",
|
|
319
|
+
"Deno",
|
|
320
|
+
"Bun",
|
|
321
|
+
"Electron",
|
|
322
|
+
"Tauri",
|
|
323
|
+
"Next.js",
|
|
324
|
+
"Nuxt.js",
|
|
325
|
+
"Gatsby",
|
|
326
|
+
"Express.js",
|
|
327
|
+
"NestJS",
|
|
328
|
+
"FastAPI",
|
|
329
|
+
"Django",
|
|
330
|
+
"Flask",
|
|
331
|
+
"Ruby on Rails",
|
|
332
|
+
"Spring Boot",
|
|
333
|
+
"Laravel",
|
|
334
|
+
"Phoenix",
|
|
335
|
+
"Actix",
|
|
336
|
+
"Axum",
|
|
337
|
+
"Rocket",
|
|
338
|
+
"Flutter",
|
|
339
|
+
"React Native",
|
|
340
|
+
"Ionic",
|
|
341
|
+
"Xamarin",
|
|
342
|
+
"Expo",
|
|
343
|
+
"Unity",
|
|
344
|
+
"Unreal Engine",
|
|
345
|
+
"ESLint",
|
|
346
|
+
"Prettier",
|
|
347
|
+
"Biome",
|
|
348
|
+
"oxc",
|
|
349
|
+
"swc",
|
|
350
|
+
"Webpack",
|
|
351
|
+
"Vite",
|
|
352
|
+
"Babel",
|
|
353
|
+
"Workbox",
|
|
354
|
+
"Rollup",
|
|
355
|
+
"Parcel",
|
|
356
|
+
"esbuild",
|
|
357
|
+
"Turbo",
|
|
358
|
+
"Turborepo",
|
|
359
|
+
"Nx",
|
|
360
|
+
"Lerna",
|
|
361
|
+
"Rush",
|
|
362
|
+
"npm",
|
|
363
|
+
"yarn",
|
|
364
|
+
"pnpm",
|
|
365
|
+
"bun",
|
|
366
|
+
"bower",
|
|
367
|
+
"composer",
|
|
368
|
+
"pip",
|
|
369
|
+
"conda",
|
|
370
|
+
"Maven",
|
|
371
|
+
"Gradle",
|
|
372
|
+
"SBT",
|
|
373
|
+
"Cargo",
|
|
374
|
+
"homebrew",
|
|
375
|
+
"chocolatey",
|
|
376
|
+
"CocoaPods",
|
|
377
|
+
"Carthage",
|
|
378
|
+
"Swift Package Manager",
|
|
379
|
+
"VS Code",
|
|
380
|
+
"Visual Studio Code",
|
|
381
|
+
"Vim",
|
|
382
|
+
"Neovim",
|
|
383
|
+
"Emacs",
|
|
384
|
+
"Sublime Text",
|
|
385
|
+
"Atom",
|
|
386
|
+
"Brackets",
|
|
387
|
+
"brackets",
|
|
388
|
+
"Visual Studio",
|
|
389
|
+
"IntelliJ IDEA",
|
|
390
|
+
"WebStorm",
|
|
391
|
+
"PHPStorm",
|
|
392
|
+
"PyCharm",
|
|
393
|
+
"Android Studio",
|
|
394
|
+
"Xcode",
|
|
395
|
+
"Docker",
|
|
396
|
+
"Kubernetes",
|
|
397
|
+
"Helm",
|
|
398
|
+
"CI / CD",
|
|
399
|
+
"DevOps",
|
|
400
|
+
"GitOps",
|
|
401
|
+
"IaC",
|
|
402
|
+
"Infrastructure as Code",
|
|
403
|
+
"SaaS",
|
|
404
|
+
"PaaS",
|
|
405
|
+
"IaaS",
|
|
406
|
+
"CDN",
|
|
407
|
+
"Load Balancer",
|
|
408
|
+
"API Gateway",
|
|
409
|
+
"Microservices",
|
|
410
|
+
"Serverless",
|
|
411
|
+
"Lambda",
|
|
412
|
+
"Cloud Functions",
|
|
413
|
+
"Container Registry",
|
|
414
|
+
"Prometheus",
|
|
415
|
+
"Grafana",
|
|
416
|
+
"Terraform",
|
|
417
|
+
"Ansible",
|
|
418
|
+
"Jenkins",
|
|
419
|
+
"GitHub Actions",
|
|
420
|
+
"GitLab CI",
|
|
421
|
+
"CircleCI",
|
|
422
|
+
"Travis CI",
|
|
423
|
+
"Azure DevOps",
|
|
424
|
+
"TeamCity",
|
|
425
|
+
"Bamboo",
|
|
426
|
+
"Buildkite",
|
|
427
|
+
"Drone CI",
|
|
428
|
+
"AWS",
|
|
429
|
+
"Amazon Web Services",
|
|
430
|
+
"Azure",
|
|
431
|
+
"GCP",
|
|
432
|
+
"Google Cloud Platform",
|
|
433
|
+
"Heroku",
|
|
434
|
+
"Vercel",
|
|
435
|
+
"Netlify",
|
|
436
|
+
"Railway",
|
|
437
|
+
"Render",
|
|
438
|
+
"Fly.io",
|
|
439
|
+
"Cloudflare",
|
|
440
|
+
"DigitalOcean",
|
|
441
|
+
"API",
|
|
442
|
+
"APIs",
|
|
443
|
+
"REST",
|
|
444
|
+
"RESTful",
|
|
445
|
+
"gRPC",
|
|
446
|
+
"HTTP",
|
|
447
|
+
"HTTPS",
|
|
448
|
+
"WebSocket",
|
|
449
|
+
"SOAP",
|
|
450
|
+
"XML-RPC",
|
|
451
|
+
"JSON-RPC",
|
|
452
|
+
"CLI",
|
|
453
|
+
"SDK",
|
|
454
|
+
"URL",
|
|
455
|
+
"URI",
|
|
456
|
+
"UUID",
|
|
457
|
+
"GUID",
|
|
458
|
+
"CRUD",
|
|
459
|
+
"ACID",
|
|
460
|
+
"BASE",
|
|
461
|
+
"CAP Theorem",
|
|
462
|
+
"JWT",
|
|
463
|
+
"OAuth",
|
|
464
|
+
"OAuth2",
|
|
465
|
+
"OpenID Connect",
|
|
466
|
+
"SAML",
|
|
467
|
+
"SSO",
|
|
468
|
+
"MFA",
|
|
469
|
+
"2FA",
|
|
470
|
+
"CORS",
|
|
471
|
+
"CSRF",
|
|
472
|
+
"XSS",
|
|
473
|
+
"SSH",
|
|
474
|
+
"FTP",
|
|
475
|
+
"SFTP",
|
|
476
|
+
"SMTP",
|
|
477
|
+
"IMAP",
|
|
478
|
+
"POP3",
|
|
479
|
+
"TCP",
|
|
480
|
+
"UDP",
|
|
481
|
+
"IP",
|
|
482
|
+
"IPv4",
|
|
483
|
+
"IPv6",
|
|
484
|
+
"DNS",
|
|
485
|
+
"DHCP",
|
|
486
|
+
"VPN",
|
|
487
|
+
"SSL",
|
|
488
|
+
"TLS",
|
|
489
|
+
"LDAP",
|
|
490
|
+
"PostgreSQL",
|
|
491
|
+
"MySQL",
|
|
492
|
+
"SQLite",
|
|
493
|
+
"MariaDB",
|
|
494
|
+
"Oracle",
|
|
495
|
+
"SQL Server",
|
|
496
|
+
"CockroachDB",
|
|
497
|
+
"PlanetScale",
|
|
498
|
+
"Neon",
|
|
499
|
+
"NoSQL",
|
|
500
|
+
"MongoDB",
|
|
501
|
+
"DynamoDB",
|
|
502
|
+
"Cassandra",
|
|
503
|
+
"CouchDB",
|
|
504
|
+
"Neo4j",
|
|
505
|
+
"ArangoDB",
|
|
506
|
+
"FaunaDB",
|
|
507
|
+
"Firebase",
|
|
508
|
+
"Supabase",
|
|
509
|
+
"Redis",
|
|
510
|
+
"ElasticSearch",
|
|
511
|
+
"Solr",
|
|
512
|
+
"InfluxDB",
|
|
513
|
+
"TimescaleDB",
|
|
514
|
+
"Prisma",
|
|
515
|
+
"TypeORM",
|
|
516
|
+
"Sequelize",
|
|
517
|
+
"Mongoose",
|
|
518
|
+
"Drizzle",
|
|
519
|
+
"Knex.js",
|
|
520
|
+
"Objection.js",
|
|
521
|
+
"Bookshelf.js",
|
|
522
|
+
"QA",
|
|
523
|
+
"QC",
|
|
524
|
+
"TDD",
|
|
525
|
+
"BDD",
|
|
526
|
+
"E2E",
|
|
527
|
+
"Unit Testing",
|
|
528
|
+
"Integration Testing",
|
|
529
|
+
"Jest",
|
|
530
|
+
"Mocha",
|
|
531
|
+
"Chai",
|
|
532
|
+
"Jasmine",
|
|
533
|
+
"Karma",
|
|
534
|
+
"Vitest",
|
|
535
|
+
"Ava",
|
|
536
|
+
"Tape",
|
|
537
|
+
"Cypress",
|
|
538
|
+
"Playwright",
|
|
539
|
+
"Selenium",
|
|
540
|
+
"Puppeteer",
|
|
541
|
+
"WebDriver",
|
|
542
|
+
"TestCafe",
|
|
543
|
+
"SonarQube",
|
|
544
|
+
"Husky",
|
|
545
|
+
"lint-staged",
|
|
546
|
+
"commitizen",
|
|
547
|
+
"semantic-release",
|
|
548
|
+
"Codecov",
|
|
549
|
+
"CodeClimate",
|
|
550
|
+
"TensorFlow",
|
|
551
|
+
"PyTorch",
|
|
552
|
+
"Keras",
|
|
553
|
+
"Scikit-learn",
|
|
554
|
+
"Pandas",
|
|
555
|
+
"NumPy",
|
|
556
|
+
"OpenCV",
|
|
557
|
+
"Hugging Face",
|
|
558
|
+
"LangChain",
|
|
559
|
+
"OpenAI",
|
|
560
|
+
"Anthropic",
|
|
561
|
+
"Jupyter",
|
|
562
|
+
"MLflow",
|
|
563
|
+
"Weights & Biases",
|
|
564
|
+
"CUDA",
|
|
565
|
+
"ONNX",
|
|
566
|
+
"GPT",
|
|
567
|
+
"BERT",
|
|
568
|
+
"Transformer",
|
|
569
|
+
"Claude",
|
|
570
|
+
"Gemini",
|
|
571
|
+
"LLaMA",
|
|
572
|
+
"Stable Diffusion",
|
|
573
|
+
"DALL-E",
|
|
574
|
+
"Midjourney",
|
|
575
|
+
"AutoML",
|
|
576
|
+
"ETL",
|
|
577
|
+
"ELT",
|
|
578
|
+
"Big Data",
|
|
579
|
+
"Data Lake",
|
|
580
|
+
"Data Warehouse",
|
|
581
|
+
"OLAP",
|
|
582
|
+
"OLTP",
|
|
583
|
+
"Apache Spark",
|
|
584
|
+
"Apache Kafka",
|
|
585
|
+
"Apache Airflow",
|
|
586
|
+
"Hadoop",
|
|
587
|
+
"Snowflake",
|
|
588
|
+
"Databricks",
|
|
589
|
+
"Tableau",
|
|
590
|
+
"Power BI",
|
|
591
|
+
"Looker",
|
|
592
|
+
"OWASP",
|
|
593
|
+
"SAST",
|
|
594
|
+
"DAST",
|
|
595
|
+
"IAST",
|
|
596
|
+
"SCA",
|
|
597
|
+
"Penetration Testing",
|
|
598
|
+
"Vulnerability Assessment",
|
|
599
|
+
"RBAC",
|
|
600
|
+
"ABAC",
|
|
601
|
+
"Zero Trust",
|
|
602
|
+
"PKI",
|
|
603
|
+
"HSM",
|
|
604
|
+
"WAF",
|
|
605
|
+
"DDoS",
|
|
606
|
+
"PWA",
|
|
607
|
+
"SPA",
|
|
608
|
+
"SSR",
|
|
609
|
+
"SSG",
|
|
610
|
+
"CSR",
|
|
611
|
+
"JAMstack",
|
|
612
|
+
"Headless CMS",
|
|
613
|
+
"Edge Computing",
|
|
614
|
+
"WebAssembly",
|
|
615
|
+
"WASM",
|
|
616
|
+
"Service Worker",
|
|
617
|
+
"Web Components",
|
|
618
|
+
"Micro Frontends",
|
|
619
|
+
"BFF",
|
|
620
|
+
"Backend for Frontend",
|
|
621
|
+
"GraphQL",
|
|
622
|
+
"tRPC",
|
|
623
|
+
"gRPC-Web",
|
|
624
|
+
"WebRTC",
|
|
625
|
+
"WebGL",
|
|
626
|
+
"WebGPU",
|
|
627
|
+
"Material UI",
|
|
628
|
+
"Ant Design",
|
|
629
|
+
"Chakra UI",
|
|
630
|
+
"React Bootstrap",
|
|
631
|
+
"Semantic UI React",
|
|
632
|
+
"Blueprint",
|
|
633
|
+
"Mantine",
|
|
634
|
+
"NextUI",
|
|
635
|
+
"Arco Design",
|
|
636
|
+
"Tailwind CSS",
|
|
637
|
+
"Bootstrap",
|
|
638
|
+
"Bulma",
|
|
639
|
+
"Foundation",
|
|
640
|
+
"Semantic UI",
|
|
641
|
+
"Materialize",
|
|
642
|
+
"Spectre.css",
|
|
643
|
+
"Tachyons",
|
|
644
|
+
"PureCSS",
|
|
645
|
+
"styled-components",
|
|
646
|
+
"CSS-in-JS",
|
|
647
|
+
"Emotion",
|
|
648
|
+
"JSS",
|
|
649
|
+
"Styled System",
|
|
650
|
+
"Stitches",
|
|
651
|
+
"Vanilla Extract",
|
|
652
|
+
"Linaria",
|
|
653
|
+
"Aphrodite",
|
|
654
|
+
"Glamorous",
|
|
655
|
+
"Radium",
|
|
656
|
+
"Git",
|
|
657
|
+
"Mercurial",
|
|
658
|
+
"SVN",
|
|
659
|
+
"GitHub",
|
|
660
|
+
"GitLab",
|
|
661
|
+
"Bitbucket",
|
|
662
|
+
"Pull Request",
|
|
663
|
+
"Merge Request",
|
|
664
|
+
"Code Review",
|
|
665
|
+
"Pair Programming",
|
|
666
|
+
"Mob Programming",
|
|
667
|
+
"Docusaurus",
|
|
668
|
+
"GitBook",
|
|
669
|
+
"VitePress",
|
|
670
|
+
"VuePress",
|
|
671
|
+
"Docsify",
|
|
672
|
+
"MkDocs",
|
|
673
|
+
"Sphinx",
|
|
674
|
+
"Jekyll",
|
|
675
|
+
"Hugo",
|
|
676
|
+
"Eleventy",
|
|
677
|
+
"Hexo",
|
|
678
|
+
"Zola",
|
|
679
|
+
"Swagger",
|
|
680
|
+
"OpenAPI",
|
|
681
|
+
"Postman",
|
|
682
|
+
"Insomnia",
|
|
683
|
+
"Redoc",
|
|
684
|
+
"Stoplight",
|
|
685
|
+
"FAQ"
|
|
686
|
+
];
|
|
687
|
+
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/resources/minor-words.ts
|
|
690
|
+
const articles = [
|
|
691
|
+
"a",
|
|
692
|
+
"an",
|
|
693
|
+
"the"
|
|
694
|
+
];
|
|
695
|
+
const conjunctions = [
|
|
696
|
+
"for",
|
|
697
|
+
"and",
|
|
698
|
+
"nor",
|
|
699
|
+
"but",
|
|
700
|
+
"or",
|
|
701
|
+
"yet",
|
|
702
|
+
"so"
|
|
703
|
+
];
|
|
704
|
+
const prepositions = [
|
|
705
|
+
"a",
|
|
706
|
+
"as",
|
|
707
|
+
"at",
|
|
708
|
+
"by",
|
|
709
|
+
"ex",
|
|
710
|
+
"in",
|
|
711
|
+
"of",
|
|
712
|
+
"on",
|
|
713
|
+
"re",
|
|
714
|
+
"to",
|
|
715
|
+
"up"
|
|
716
|
+
];
|
|
717
|
+
const defaultMinorWords = [
|
|
718
|
+
...articles,
|
|
719
|
+
...conjunctions,
|
|
720
|
+
...prepositions
|
|
721
|
+
];
|
|
722
|
+
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/rules/heading-casing.ts
|
|
725
|
+
/**
|
|
726
|
+
* Parse preserve words and phrases from the options
|
|
727
|
+
* - Single words are added to preserveWords
|
|
728
|
+
* - Multi-word phrases are added to preservePhrases and added to preserveWords with no spaces
|
|
729
|
+
*/
|
|
730
|
+
function parsePreserveWords(preserveWordsOption) {
|
|
731
|
+
const preserveWords = /* @__PURE__ */ new Map();
|
|
732
|
+
/**
|
|
733
|
+
* Add a single word to the preserveWords map
|
|
734
|
+
*/
|
|
735
|
+
function addPreserveWord(word) {
|
|
736
|
+
const lowerWord = word.toLowerCase();
|
|
737
|
+
let list = preserveWords.get(lowerWord);
|
|
738
|
+
if (!list) {
|
|
739
|
+
list = [];
|
|
740
|
+
preserveWords.set(lowerWord, list);
|
|
741
|
+
}
|
|
742
|
+
list.push(word);
|
|
743
|
+
}
|
|
744
|
+
const preservePhrases = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const word of preserveWordsOption) {
|
|
746
|
+
const splitted = word.split(/\s+/);
|
|
747
|
+
if (splitted.length <= 1) addPreserveWord(word);
|
|
748
|
+
else {
|
|
749
|
+
preservePhrases.set(word, splitted);
|
|
750
|
+
addPreserveWord(splitted.join(""));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
preserveWords,
|
|
755
|
+
preservePhrases: [...preservePhrases.values()].sort((a, b) => b.length - a.length)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Parse text into words with offsets
|
|
760
|
+
*/
|
|
761
|
+
function parseText(text, firstNode, lastNode) {
|
|
762
|
+
const words = [];
|
|
763
|
+
const pattern = /(\w+(?:[^\s\w]\w+)*|:\w+:|[^\s\w]+|\s+)/gu;
|
|
764
|
+
let match;
|
|
765
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
766
|
+
const token = match[1];
|
|
767
|
+
if (/^\s+$/.test(token)) continue;
|
|
768
|
+
const punctuation = /^(?::\w+:|[^\s\w]+)$/.test(token);
|
|
769
|
+
words.push({
|
|
770
|
+
word: token,
|
|
771
|
+
offset: match.index,
|
|
772
|
+
punctuation,
|
|
773
|
+
first: false,
|
|
774
|
+
last: false
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
if (firstNode) {
|
|
778
|
+
for (const w of words) if (!w.punctuation) {
|
|
779
|
+
w.first = true;
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (lastNode) for (let i = words.length - 1; i >= 0; i--) {
|
|
784
|
+
const w = words[i];
|
|
785
|
+
if (!w.punctuation) {
|
|
786
|
+
w.last = true;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return words;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Convert a single word based on case style and context
|
|
794
|
+
*/
|
|
795
|
+
function convertWord({ word, first, last }, caseStyle, minorWords) {
|
|
796
|
+
if (caseStyle === "Title Case") {
|
|
797
|
+
if (first || last) return {
|
|
798
|
+
word: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
|
|
799
|
+
isMinorWord: false
|
|
800
|
+
};
|
|
801
|
+
if (minorWords.some((minorWord) => minorWord.toLowerCase() === word.toLowerCase())) return {
|
|
802
|
+
word: word.toLowerCase(),
|
|
803
|
+
isMinorWord: true
|
|
804
|
+
};
|
|
805
|
+
return {
|
|
806
|
+
word: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
|
|
807
|
+
isMinorWord: false
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
if (first) return {
|
|
811
|
+
word: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
|
|
812
|
+
isMinorWord: false
|
|
813
|
+
};
|
|
814
|
+
return {
|
|
815
|
+
word: word.toLowerCase(),
|
|
816
|
+
isMinorWord: false
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
var heading_casing_default = createRule("heading-casing", {
|
|
820
|
+
meta: {
|
|
821
|
+
type: "suggestion",
|
|
822
|
+
docs: {
|
|
823
|
+
description: "enforce consistent casing in headings.",
|
|
824
|
+
categories: [],
|
|
825
|
+
listCategory: "Stylistic"
|
|
826
|
+
},
|
|
827
|
+
fixable: "code",
|
|
828
|
+
hasSuggestions: false,
|
|
829
|
+
schema: [{
|
|
830
|
+
type: "object",
|
|
831
|
+
properties: {
|
|
832
|
+
style: { enum: ["Title Case", "Sentence case"] },
|
|
833
|
+
preserveWords: {
|
|
834
|
+
type: "array",
|
|
835
|
+
items: { type: "string" },
|
|
836
|
+
description: "Words that should be preserved as-is (case-insensitive matching)"
|
|
837
|
+
},
|
|
838
|
+
ignorePatterns: {
|
|
839
|
+
type: "array",
|
|
840
|
+
items: { type: "string" },
|
|
841
|
+
description: "Regular expression patterns for words to ignore during casing checks"
|
|
842
|
+
},
|
|
843
|
+
minorWords: {
|
|
844
|
+
type: "array",
|
|
845
|
+
items: { type: "string" },
|
|
846
|
+
description: "Words that should not be capitalized in Title Case (unless they're the first or last word)"
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
additionalProperties: false
|
|
850
|
+
}],
|
|
851
|
+
messages: {
|
|
852
|
+
expectedTitleCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case).",
|
|
853
|
+
expectedTitleCaseMinorWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (Title Case - minor word).",
|
|
854
|
+
expectedSentenceCase: "Expected \"{{actual}}\" to be \"{{expected}}\" (Sentence case).",
|
|
855
|
+
expectedPreserveWord: "Expected \"{{actual}}\" to be \"{{expected}}\" (preserved word)."
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
create(context) {
|
|
859
|
+
const sourceCode = context.sourceCode;
|
|
860
|
+
const caseStyle = context.options[0]?.style || "Title Case";
|
|
861
|
+
const { preserveWords, preservePhrases } = parsePreserveWords(context.options[0]?.preserveWords || defaultPreserveWords);
|
|
862
|
+
const minorWords = context.options[0]?.minorWords || defaultMinorWords;
|
|
863
|
+
const ignorePatterns = (context.options[0]?.ignorePatterns || [
|
|
864
|
+
"/^v\\d+/u",
|
|
865
|
+
"/\\w+\\.[a-z\\d]+$/u",
|
|
866
|
+
"/\\w*(?:API|Api)$/u",
|
|
867
|
+
"/\\w*(?:SDK|Sdk)$/u",
|
|
868
|
+
"/\\w*(?:CLI|Cli)$/u"
|
|
869
|
+
]).map((pattern) => {
|
|
870
|
+
if (isRegExp(pattern)) return toRegExp(pattern);
|
|
871
|
+
try {
|
|
872
|
+
return new RegExp(pattern, "v");
|
|
873
|
+
} catch {}
|
|
874
|
+
try {
|
|
875
|
+
return new RegExp(pattern, "u");
|
|
876
|
+
} catch {}
|
|
877
|
+
return new RegExp(pattern);
|
|
878
|
+
});
|
|
879
|
+
/**
|
|
880
|
+
* Check text node and report word-level errors
|
|
881
|
+
*/
|
|
882
|
+
function checkTextNode(node, firstNode, lastNode) {
|
|
883
|
+
const text = sourceCode.getText(node);
|
|
884
|
+
const wordAndOffsets = parseText(text, firstNode, lastNode);
|
|
885
|
+
const processed = /* @__PURE__ */ new Set();
|
|
886
|
+
for (let index = 0; index < wordAndOffsets.length; index++) {
|
|
887
|
+
if (processed.has(index)) continue;
|
|
888
|
+
processed.add(index);
|
|
889
|
+
const wordAndOffset = wordAndOffsets[index];
|
|
890
|
+
if (wordAndOffset.punctuation) continue;
|
|
891
|
+
if (ignorePatterns.some((pattern) => pattern.test(wordAndOffset.word))) continue;
|
|
892
|
+
const preservePhrase = findPreservePhrase(wordAndOffsets, index);
|
|
893
|
+
if (preservePhrase) {
|
|
894
|
+
for (let wordIndex = 0; wordIndex < preservePhrase.length; wordIndex++) {
|
|
895
|
+
processed.add(index + wordIndex);
|
|
896
|
+
verifyWord(wordAndOffsets[index + wordIndex], preservePhrase[wordIndex], "preserved");
|
|
897
|
+
}
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const preserveWordList = preserveWords.get(wordAndOffset.word.toLowerCase());
|
|
901
|
+
if (preserveWordList) {
|
|
902
|
+
if (!preserveWordList.some((w) => w === wordAndOffset.word)) verifyWord(wordAndOffset, preserveWordList[0], "preserved");
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
const expectedWordResult = convertWord(wordAndOffset, caseStyle, minorWords);
|
|
906
|
+
verifyWord(wordAndOffset, expectedWordResult.word, expectedWordResult.isMinorWord ? "minor" : "normal");
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Verify a single word against the expected casing
|
|
910
|
+
*/
|
|
911
|
+
function verifyWord(wordAndOffset, expectedWord, wordType = "normal") {
|
|
912
|
+
const { word, offset } = wordAndOffset;
|
|
913
|
+
if (word === expectedWord) return;
|
|
914
|
+
const nodeLoc = sourceCode.getLoc(node);
|
|
915
|
+
const beforeLines = text.slice(0, offset).split(/\n/u);
|
|
916
|
+
const line = nodeLoc.start.line + beforeLines.length - 1;
|
|
917
|
+
const column = (beforeLines.length === 1 ? nodeLoc.start.column : 1) + (beforeLines.at(-1) || "").length;
|
|
918
|
+
const nodeRange = sourceCode.getRange(node);
|
|
919
|
+
const wordRange = [nodeRange[0] + offset, nodeRange[0] + offset + word.length];
|
|
920
|
+
context.report({
|
|
921
|
+
node,
|
|
922
|
+
messageId: wordType === "preserved" ? "expectedPreserveWord" : caseStyle === "Title Case" ? wordType === "minor" ? "expectedTitleCaseMinorWord" : "expectedTitleCase" : "expectedSentenceCase",
|
|
923
|
+
data: {
|
|
924
|
+
actual: word,
|
|
925
|
+
expected: expectedWord
|
|
926
|
+
},
|
|
927
|
+
loc: {
|
|
928
|
+
start: {
|
|
929
|
+
line,
|
|
930
|
+
column
|
|
931
|
+
},
|
|
932
|
+
end: {
|
|
933
|
+
line,
|
|
934
|
+
column: column + word.length
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
fix(fixer) {
|
|
938
|
+
return fixer.replaceTextRange(wordRange, expectedWord);
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Find a preserve phrase starting from the given index
|
|
945
|
+
* Returns the longest matching phrase or null if no match is found
|
|
946
|
+
*/
|
|
947
|
+
function findPreservePhrase(wordAndOffsets, index) {
|
|
948
|
+
const firstWord = wordAndOffsets[index];
|
|
949
|
+
if (firstWord.punctuation) return null;
|
|
950
|
+
const firstLowerWord = firstWord.word.toLowerCase();
|
|
951
|
+
let returnCandidate = null;
|
|
952
|
+
let subWords = null;
|
|
953
|
+
for (const phrase of preservePhrases) {
|
|
954
|
+
if (returnCandidate && returnCandidate.preservePhrase.length !== phrase.length) break;
|
|
955
|
+
if (firstLowerWord !== phrase[0].toLowerCase()) continue;
|
|
956
|
+
if (!subWords || subWords.length !== phrase.length) subWords = wordAndOffsets.slice(index, index + phrase.length).map((wo) => wo.word);
|
|
957
|
+
if (subWords.length === phrase.length && subWords.every((word, i) => word.toLowerCase() === phrase[i].toLowerCase())) {
|
|
958
|
+
let matchCount = 0;
|
|
959
|
+
for (let i = 0; i < subWords.length; i++) {
|
|
960
|
+
const word = subWords[i];
|
|
961
|
+
if (word === phrase[i]) matchCount++;
|
|
962
|
+
}
|
|
963
|
+
if (!returnCandidate || matchCount > returnCandidate.matchCount) returnCandidate = {
|
|
964
|
+
preservePhrase: phrase,
|
|
965
|
+
matchCount
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return returnCandidate?.preservePhrase ?? null;
|
|
970
|
+
}
|
|
971
|
+
return { heading(node) {
|
|
972
|
+
if (!node.children.length) return;
|
|
973
|
+
const children = node.children.filter((child) => child.type !== "text" || child.value.trim());
|
|
974
|
+
children.forEach((child, i) => {
|
|
975
|
+
if (child.type === "text") checkTextNode(child, i === 0, i === node.children.length - 1);
|
|
976
|
+
});
|
|
977
|
+
} };
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
|
|
112
981
|
//#endregion
|
|
113
982
|
//#region src/rules/no-text-backslash-linebreak.ts
|
|
114
983
|
var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebreak", {
|
|
@@ -116,7 +985,8 @@ var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebrea
|
|
|
116
985
|
type: "suggestion",
|
|
117
986
|
docs: {
|
|
118
987
|
description: "disallow text backslash at the end of a line.",
|
|
119
|
-
categories: ["recommended"]
|
|
988
|
+
categories: ["recommended"],
|
|
989
|
+
listCategory: "Preference"
|
|
120
990
|
},
|
|
121
991
|
fixable: void 0,
|
|
122
992
|
hasSuggestions: true,
|
|
@@ -172,7 +1042,8 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
172
1042
|
type: "layout",
|
|
173
1043
|
docs: {
|
|
174
1044
|
description: "disallow trailing whitespace at the end of lines in Markdown files.",
|
|
175
|
-
categories: []
|
|
1045
|
+
categories: [],
|
|
1046
|
+
listCategory: "Stylistic"
|
|
176
1047
|
},
|
|
177
1048
|
fixable: "whitespace",
|
|
178
1049
|
hasSuggestions: false,
|
|
@@ -380,7 +1251,8 @@ var prefer_inline_code_words_default = createRule("prefer-inline-code-words", {
|
|
|
380
1251
|
type: "suggestion",
|
|
381
1252
|
docs: {
|
|
382
1253
|
description: "enforce the use of inline code for specific words.",
|
|
383
|
-
categories: []
|
|
1254
|
+
categories: [],
|
|
1255
|
+
listCategory: "Preference"
|
|
384
1256
|
},
|
|
385
1257
|
fixable: "code",
|
|
386
1258
|
hasSuggestions: false,
|
|
@@ -450,7 +1322,8 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
450
1322
|
type: "layout",
|
|
451
1323
|
docs: {
|
|
452
1324
|
description: "enforce using link reference definitions instead of inline links",
|
|
453
|
-
categories: []
|
|
1325
|
+
categories: [],
|
|
1326
|
+
listCategory: "Stylistic"
|
|
454
1327
|
},
|
|
455
1328
|
fixable: "code",
|
|
456
1329
|
hasSuggestions: false,
|
|
@@ -472,7 +1345,7 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
472
1345
|
const minLinks = options.minLinks ?? 2;
|
|
473
1346
|
const definitions = [];
|
|
474
1347
|
const links = [];
|
|
475
|
-
const
|
|
1348
|
+
const references = [];
|
|
476
1349
|
const headings = [];
|
|
477
1350
|
/**
|
|
478
1351
|
* Verify links.
|
|
@@ -480,13 +1353,13 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
480
1353
|
function verify() {
|
|
481
1354
|
const resourceToNodes = /* @__PURE__ */ new Map();
|
|
482
1355
|
for (const link of links) getResourceNodes(link).links.push(link);
|
|
483
|
-
for (const
|
|
484
|
-
const definition = definitions.find((def) => def.identifier ===
|
|
485
|
-
if (definition) getResourceNodes(definition).
|
|
1356
|
+
for (const reference of references) {
|
|
1357
|
+
const definition = definitions.find((def) => def.identifier === reference.identifier);
|
|
1358
|
+
if (definition) getResourceNodes(definition).references.push(reference);
|
|
486
1359
|
}
|
|
487
1360
|
for (const definition of definitions) getResourceNodes(definition).definitions.push(definition);
|
|
488
1361
|
for (const map of resourceToNodes.values()) for (const nodes of map.values()) {
|
|
489
|
-
if (nodes.links.length === 0 || nodes.links.length + nodes.
|
|
1362
|
+
if (nodes.links.length === 0 || nodes.links.length + nodes.references.length < minLinks) continue;
|
|
490
1363
|
for (const link of nodes.links) {
|
|
491
1364
|
const linkInfo = getLinkInfo(link);
|
|
492
1365
|
if (linkInfo.label === "") continue;
|
|
@@ -497,8 +1370,16 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
497
1370
|
const definition = nodes.definitions[0];
|
|
498
1371
|
let identifier;
|
|
499
1372
|
if (definition) identifier = definition.label ?? definition.identifier;
|
|
500
|
-
else
|
|
501
|
-
|
|
1373
|
+
else {
|
|
1374
|
+
identifier = linkInfo.label.replaceAll(/[[\]]/gu, "-");
|
|
1375
|
+
if (definitions.some((def) => def.identifier === identifier)) {
|
|
1376
|
+
let seq = 1;
|
|
1377
|
+
const original = identifier;
|
|
1378
|
+
identifier = `${original}-${seq}`;
|
|
1379
|
+
while (definitions.some((def) => def.identifier === identifier)) identifier = `${original}-${++seq}`;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
yield fixer.replaceText(link, `${sourceCode.text.slice(...linkInfo.bracketsRange)}${identifier === linkInfo.label ? "" : `[${identifier}]`}`);
|
|
502
1383
|
if (!definition) {
|
|
503
1384
|
const linkRange = sourceCode.getRange(link);
|
|
504
1385
|
const nextSectionHeading = headings.find((heading) => linkRange[1] < sourceCode.getRange(heading)[0]);
|
|
@@ -529,7 +1410,7 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
529
1410
|
if (!nodes) {
|
|
530
1411
|
nodes = {
|
|
531
1412
|
links: [],
|
|
532
|
-
|
|
1413
|
+
references: [],
|
|
533
1414
|
definitions: []
|
|
534
1415
|
};
|
|
535
1416
|
map.set(title, nodes);
|
|
@@ -539,10 +1420,13 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
539
1420
|
}
|
|
540
1421
|
return {
|
|
541
1422
|
link(node) {
|
|
1423
|
+
if (sourceCode.getText(node).startsWith("[")) links.push(node);
|
|
1424
|
+
},
|
|
1425
|
+
image(node) {
|
|
542
1426
|
links.push(node);
|
|
543
1427
|
},
|
|
544
|
-
linkReference(node) {
|
|
545
|
-
|
|
1428
|
+
"linkReference, imageReference"(node) {
|
|
1429
|
+
references.push(node);
|
|
546
1430
|
},
|
|
547
1431
|
definition(node) {
|
|
548
1432
|
definitions.push(node);
|
|
@@ -559,31 +1443,70 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
559
1443
|
*/
|
|
560
1444
|
function getLinkInfo(link) {
|
|
561
1445
|
const range = sourceCode.getRange(link);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1446
|
+
if (link.type === "link") {
|
|
1447
|
+
const bracketsRange$1 = getLinkBracketsRange(link);
|
|
1448
|
+
const linkBracketsText$1 = sourceCode.text.slice(...bracketsRange$1);
|
|
1449
|
+
const linkLabelText$1 = linkBracketsText$1.slice(1, -1).trim();
|
|
1450
|
+
const urlStartIndex$1 = sourceCode.text.indexOf("(", bracketsRange$1[1]);
|
|
1451
|
+
return {
|
|
1452
|
+
label: linkLabelText$1,
|
|
1453
|
+
bracketsRange: bracketsRange$1,
|
|
1454
|
+
urlAndTitleRange: [urlStartIndex$1, range[1]]
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
const bracketsRange = getImageBracketsRange(link);
|
|
1458
|
+
const linkBracketsText = sourceCode.text.slice(...bracketsRange);
|
|
1459
|
+
const linkLabelText = linkBracketsText.slice(1, -1).trim();
|
|
1460
|
+
const urlStartIndex = sourceCode.text.indexOf("(", bracketsRange[1]);
|
|
566
1461
|
return {
|
|
567
1462
|
label: linkLabelText,
|
|
568
|
-
|
|
1463
|
+
bracketsRange,
|
|
569
1464
|
urlAndTitleRange: [urlStartIndex, range[1]]
|
|
570
1465
|
};
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const index = sourceCode.text.indexOf("]", lastRange[1]);
|
|
581
|
-
return [range[0], index + 1];
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Get the range of the link label.
|
|
1469
|
+
*/
|
|
1470
|
+
function getLinkBracketsRange(link) {
|
|
1471
|
+
const range = sourceCode.getRange(link);
|
|
1472
|
+
if (link.children.length === 0) {
|
|
1473
|
+
const index$1 = sourceCode.text.indexOf("]", range[0] + 1);
|
|
1474
|
+
return [range[0], index$1 + 1];
|
|
582
1475
|
}
|
|
1476
|
+
const lastRange = sourceCode.getRange(link.children[link.children.length - 1]);
|
|
1477
|
+
const index = sourceCode.text.indexOf("]", lastRange[1]);
|
|
1478
|
+
return [range[0], index + 1];
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Get the range of the image label.
|
|
1482
|
+
*/
|
|
1483
|
+
function getImageBracketsRange(image) {
|
|
1484
|
+
const range = sourceCode.getRange(image);
|
|
1485
|
+
const index = sourceCode.text.indexOf("]", range[0] + 2);
|
|
1486
|
+
return [range[0] + 1, index + 1];
|
|
583
1487
|
}
|
|
584
1488
|
}
|
|
585
1489
|
});
|
|
586
1490
|
|
|
1491
|
+
//#endregion
|
|
1492
|
+
//#region src/utils/url.ts
|
|
1493
|
+
/**
|
|
1494
|
+
* Utility function to check if a string is a valid URL.
|
|
1495
|
+
*/
|
|
1496
|
+
function isValidURL(url) {
|
|
1497
|
+
return Boolean(createURLSafe(url));
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Utility function to create a URL object safely.
|
|
1501
|
+
*/
|
|
1502
|
+
function createURLSafe(url) {
|
|
1503
|
+
try {
|
|
1504
|
+
return new URL(url);
|
|
1505
|
+
} catch {
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
587
1510
|
//#endregion
|
|
588
1511
|
//#region src/rules/prefer-linked-words.ts
|
|
589
1512
|
var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
@@ -591,7 +1514,8 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
591
1514
|
type: "suggestion",
|
|
592
1515
|
docs: {
|
|
593
1516
|
description: "enforce the specified word to be a link.",
|
|
594
|
-
categories: []
|
|
1517
|
+
categories: [],
|
|
1518
|
+
listCategory: "Preference"
|
|
595
1519
|
},
|
|
596
1520
|
fixable: "code",
|
|
597
1521
|
hasSuggestions: false,
|
|
@@ -684,7 +1608,7 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
684
1608
|
* Adjust link to be relative to the file.
|
|
685
1609
|
*/
|
|
686
1610
|
function adjustLink(link) {
|
|
687
|
-
if (
|
|
1611
|
+
if (isValidURL(link)) return link;
|
|
688
1612
|
if (link.startsWith("#")) return link;
|
|
689
1613
|
const absoluteLink = path.isAbsolute(link) || path.posix.isAbsolute(link) ? link : path.join(context.cwd, link);
|
|
690
1614
|
return `./${path.relative(path.dirname(context.filename), absoluteLink)}`;
|
|
@@ -692,16 +1616,302 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
692
1616
|
}
|
|
693
1617
|
});
|
|
694
1618
|
|
|
1619
|
+
//#endregion
|
|
1620
|
+
//#region src/rules/sort-definitions.ts
|
|
1621
|
+
var sort_definitions_default = createRule("sort-definitions", {
|
|
1622
|
+
meta: {
|
|
1623
|
+
type: "layout",
|
|
1624
|
+
docs: {
|
|
1625
|
+
description: "enforce a specific order for link definitions and footnote definitions",
|
|
1626
|
+
categories: [],
|
|
1627
|
+
listCategory: "Stylistic"
|
|
1628
|
+
},
|
|
1629
|
+
fixable: "code",
|
|
1630
|
+
hasSuggestions: false,
|
|
1631
|
+
schema: [{
|
|
1632
|
+
type: "object",
|
|
1633
|
+
properties: {
|
|
1634
|
+
order: {
|
|
1635
|
+
type: "array",
|
|
1636
|
+
items: { anyOf: [
|
|
1637
|
+
{ type: "string" },
|
|
1638
|
+
{
|
|
1639
|
+
type: "array",
|
|
1640
|
+
items: { type: "string" },
|
|
1641
|
+
uniqueItems: true,
|
|
1642
|
+
minItems: 1
|
|
1643
|
+
},
|
|
1644
|
+
{
|
|
1645
|
+
type: "object",
|
|
1646
|
+
properties: {
|
|
1647
|
+
match: { anyOf: [{ type: "string" }, {
|
|
1648
|
+
type: "array",
|
|
1649
|
+
items: { type: "string" },
|
|
1650
|
+
uniqueItems: true,
|
|
1651
|
+
minItems: 1
|
|
1652
|
+
}] },
|
|
1653
|
+
sort: { enum: ["alphabetical", "ignore"] }
|
|
1654
|
+
},
|
|
1655
|
+
required: ["match", "sort"],
|
|
1656
|
+
additionalProperties: false
|
|
1657
|
+
}
|
|
1658
|
+
] },
|
|
1659
|
+
uniqueItems: true,
|
|
1660
|
+
additionalItems: false
|
|
1661
|
+
},
|
|
1662
|
+
alphabetical: { type: "boolean" }
|
|
1663
|
+
},
|
|
1664
|
+
additionalProperties: false
|
|
1665
|
+
}],
|
|
1666
|
+
messages: { shouldBefore: "The definition '{{currentKey}}' should be before '{{prevKey}}'." }
|
|
1667
|
+
},
|
|
1668
|
+
create(context) {
|
|
1669
|
+
const sourceCode = context.sourceCode;
|
|
1670
|
+
const option = parseOption(context.options[0]);
|
|
1671
|
+
const group = [];
|
|
1672
|
+
const cacheText = /* @__PURE__ */ new Map();
|
|
1673
|
+
/** Get normalized text */
|
|
1674
|
+
function getDefinitionText(node) {
|
|
1675
|
+
const k = cacheText.get(node);
|
|
1676
|
+
if (k != null) return k;
|
|
1677
|
+
if (node.type === "definition") return `[${node.label || node.identifier}]: ${node.url}${printTitle(node.title)}`;
|
|
1678
|
+
let childrenText = "";
|
|
1679
|
+
if (node.children.length) {
|
|
1680
|
+
const [start] = sourceCode.getRange(node.children[0]);
|
|
1681
|
+
const [, end] = sourceCode.getRange(node.children.at(-1));
|
|
1682
|
+
childrenText = sourceCode.text.slice(start, end);
|
|
1683
|
+
}
|
|
1684
|
+
return `[^${node.identifier}]: ${childrenText}`;
|
|
1685
|
+
}
|
|
1686
|
+
/** Report */
|
|
1687
|
+
function report(node, previousNode, definitions) {
|
|
1688
|
+
const currentKey = getDefinitionText(node);
|
|
1689
|
+
const prevKey = getDefinitionText(previousNode);
|
|
1690
|
+
context.report({
|
|
1691
|
+
node,
|
|
1692
|
+
messageId: "shouldBefore",
|
|
1693
|
+
data: {
|
|
1694
|
+
currentKey,
|
|
1695
|
+
prevKey
|
|
1696
|
+
},
|
|
1697
|
+
fix(fixer) {
|
|
1698
|
+
const previousNodeIndex = definitions.indexOf(previousNode);
|
|
1699
|
+
const targetNodeIndex = definitions.indexOf(node);
|
|
1700
|
+
const previousNodes = definitions.slice(previousNodeIndex, targetNodeIndex);
|
|
1701
|
+
const before = definitions.slice(0, previousNodeIndex);
|
|
1702
|
+
const after = definitions.slice(targetNodeIndex + 1);
|
|
1703
|
+
const movedNodes = [
|
|
1704
|
+
...before,
|
|
1705
|
+
node,
|
|
1706
|
+
...previousNodes,
|
|
1707
|
+
...after
|
|
1708
|
+
];
|
|
1709
|
+
return movedNodes.map((moveNode, index) => {
|
|
1710
|
+
let text = sourceCode.getText(moveNode);
|
|
1711
|
+
if (moveNode.type === "definition" && index > 0) {
|
|
1712
|
+
if (movedNodes[index - 1].type === "footnoteDefinition") {
|
|
1713
|
+
const footnoteLoc = sourceCode.getLoc(definitions[index - 1]);
|
|
1714
|
+
const linkLoc = sourceCode.getLoc(definitions[index]);
|
|
1715
|
+
if (linkLoc.start.line - footnoteLoc.end.line <= 1) text = `\n${text}`;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return fixer.replaceText(definitions[index], text);
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Verify definitions and footnote definitions.
|
|
1725
|
+
*/
|
|
1726
|
+
function verify(definitions) {
|
|
1727
|
+
if (definitions.length === 0) return;
|
|
1728
|
+
const validPreviousNodes = [];
|
|
1729
|
+
for (const definition of definitions) {
|
|
1730
|
+
if (option.ignore(definition)) continue;
|
|
1731
|
+
const invalidPreviousNode = validPreviousNodes.find((previousNode) => option.compare(previousNode, definition) > 0);
|
|
1732
|
+
if (invalidPreviousNode) {
|
|
1733
|
+
report(definition, invalidPreviousNode, definitions);
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
validPreviousNodes.push(definition);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
return {
|
|
1740
|
+
"*"(node) {
|
|
1741
|
+
const last = group.at(-1);
|
|
1742
|
+
if (last && (node.type !== "definition" && node.type !== "footnoteDefinition" || sourceCode.getParent(node) !== sourceCode.getParent(last))) {
|
|
1743
|
+
const range = sourceCode.getRange(node);
|
|
1744
|
+
const lastDefinitionRange = sourceCode.getRange(node);
|
|
1745
|
+
if (lastDefinitionRange[1] <= range[0]) {
|
|
1746
|
+
verify(group);
|
|
1747
|
+
group.length = 0;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (node.type === "definition" || node.type === "footnoteDefinition") group.push(node);
|
|
1751
|
+
},
|
|
1752
|
+
"root:exit"() {
|
|
1753
|
+
verify(group);
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
/** Parse options */
|
|
1757
|
+
function parseOption(userOption) {
|
|
1758
|
+
const order = userOption?.order ?? [{
|
|
1759
|
+
match: String.raw`!/^\[\\^/u`,
|
|
1760
|
+
sort: "alphabetical"
|
|
1761
|
+
}, {
|
|
1762
|
+
match: String.raw`/./u`,
|
|
1763
|
+
sort: "alphabetical"
|
|
1764
|
+
}];
|
|
1765
|
+
const compiled = order.map(compileOption);
|
|
1766
|
+
return {
|
|
1767
|
+
ignore: (node) => {
|
|
1768
|
+
return !compiled.some((c) => c.match(node));
|
|
1769
|
+
},
|
|
1770
|
+
compare: (a, b) => {
|
|
1771
|
+
for (const c of compiled) {
|
|
1772
|
+
const matchA = c.match(a);
|
|
1773
|
+
const matchB = c.match(b);
|
|
1774
|
+
if (matchA && matchB) {
|
|
1775
|
+
if (c.sort === "alphabetical") {
|
|
1776
|
+
const textA = getDefinitionText(a);
|
|
1777
|
+
const textB = getDefinitionText(b);
|
|
1778
|
+
if (textA === textB) return 0;
|
|
1779
|
+
return textA < textB ? -1 : 1;
|
|
1780
|
+
}
|
|
1781
|
+
return 0;
|
|
1782
|
+
}
|
|
1783
|
+
if (matchA) return -1;
|
|
1784
|
+
if (matchB) return 1;
|
|
1785
|
+
}
|
|
1786
|
+
throw new Error("Illegal state");
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
}
|
|
1790
|
+
/** Compile order option */
|
|
1791
|
+
function compileOption(orderOption) {
|
|
1792
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1793
|
+
const compiled = compileOptionWithoutCache(orderOption);
|
|
1794
|
+
return {
|
|
1795
|
+
match: (node) => {
|
|
1796
|
+
const cached = cache.get(node);
|
|
1797
|
+
if (cached != null) return cached;
|
|
1798
|
+
const result = compiled.match(node);
|
|
1799
|
+
cache.set(node, result);
|
|
1800
|
+
return result;
|
|
1801
|
+
},
|
|
1802
|
+
sort: compiled.sort
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/** Compile order option without cache */
|
|
1806
|
+
function compileOptionWithoutCache(orderOption) {
|
|
1807
|
+
if (typeof orderOption === "string") {
|
|
1808
|
+
const match$1 = compileMatcher([orderOption]);
|
|
1809
|
+
return {
|
|
1810
|
+
match: match$1,
|
|
1811
|
+
sort: "ignore"
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
if (Array.isArray(orderOption)) {
|
|
1815
|
+
const match$1 = compileMatcher(orderOption);
|
|
1816
|
+
return {
|
|
1817
|
+
match: match$1,
|
|
1818
|
+
sort: "ignore"
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
const { match } = compileOptionWithoutCache(orderOption.match);
|
|
1822
|
+
return {
|
|
1823
|
+
match,
|
|
1824
|
+
sort: orderOption.sort || "ignore"
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
/** Compile matcher */
|
|
1828
|
+
function compileMatcher(pattern) {
|
|
1829
|
+
const rules$3 = [];
|
|
1830
|
+
for (const p of pattern) {
|
|
1831
|
+
let negative, patternStr;
|
|
1832
|
+
if (p.startsWith("!")) {
|
|
1833
|
+
negative = true;
|
|
1834
|
+
patternStr = p.substring(1);
|
|
1835
|
+
} else {
|
|
1836
|
+
negative = false;
|
|
1837
|
+
patternStr = p;
|
|
1838
|
+
}
|
|
1839
|
+
const regex = toRegExp(patternStr);
|
|
1840
|
+
if (isRegExp(patternStr)) rules$3.push({
|
|
1841
|
+
negative,
|
|
1842
|
+
match: (node) => regex.test(getDefinitionText(node))
|
|
1843
|
+
});
|
|
1844
|
+
else rules$3.push({
|
|
1845
|
+
negative,
|
|
1846
|
+
match: (node) => {
|
|
1847
|
+
if (node.label === patternStr || node.identifier === patternStr) return true;
|
|
1848
|
+
if (node.type === "definition") {
|
|
1849
|
+
if (node.url === patternStr) return true;
|
|
1850
|
+
if (isValidURL(patternStr)) {
|
|
1851
|
+
const normalizedPattern = normalizedURL(patternStr);
|
|
1852
|
+
const normalizedUrl = normalizedURL(node.url);
|
|
1853
|
+
if (normalizedUrl.startsWith(normalizedPattern)) return true;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return regex.test(getDefinitionText(node));
|
|
1857
|
+
}
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
return (node) => {
|
|
1861
|
+
let result = Boolean(rules$3[0]?.negative);
|
|
1862
|
+
for (const { negative, match } of rules$3) {
|
|
1863
|
+
if (result === !negative) continue;
|
|
1864
|
+
if (match(node)) result = !negative;
|
|
1865
|
+
}
|
|
1866
|
+
return result;
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
});
|
|
1871
|
+
/**
|
|
1872
|
+
* Print the title with quotes.
|
|
1873
|
+
*/
|
|
1874
|
+
function printTitle(title) {
|
|
1875
|
+
if (!title) return "";
|
|
1876
|
+
let titleToPrint = title.replaceAll(/\\(?=["')])/gu, "");
|
|
1877
|
+
if (titleToPrint.includes("\"") && titleToPrint.includes("'") && !titleToPrint.includes(")")) return ` (${titleToPrint})`;
|
|
1878
|
+
const quote = getQuote(titleToPrint);
|
|
1879
|
+
titleToPrint = titleToPrint.replaceAll("\\", "\\\\");
|
|
1880
|
+
titleToPrint = titleToPrint.replaceAll(quote, `\\${quote}`);
|
|
1881
|
+
return ` ${quote}${titleToPrint}${quote}`;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Get the preferred quote for a string.
|
|
1885
|
+
*/
|
|
1886
|
+
function getQuote(text) {
|
|
1887
|
+
let doubleQuoteCount = 0;
|
|
1888
|
+
let singleQuoteCount = 0;
|
|
1889
|
+
for (const character of text) if (character === "\"") doubleQuoteCount++;
|
|
1890
|
+
else if (character === "'") singleQuoteCount++;
|
|
1891
|
+
return doubleQuoteCount > singleQuoteCount ? "'" : "\"";
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Normalize a URL by ensuring it ends with a slash.
|
|
1895
|
+
*/
|
|
1896
|
+
function normalizedURL(url) {
|
|
1897
|
+
const urlObj = createURLSafe(url);
|
|
1898
|
+
if (!urlObj) return url;
|
|
1899
|
+
return urlObj.href.endsWith("/") ? urlObj.href : `${urlObj.href}/`;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
695
1902
|
//#endregion
|
|
696
1903
|
//#region src/utils/rules.ts
|
|
697
1904
|
const rules$1 = [
|
|
1905
|
+
canonical_code_block_language_default,
|
|
698
1906
|
definitions_last_default,
|
|
699
1907
|
hard_linebreak_style_default,
|
|
1908
|
+
heading_casing_default,
|
|
700
1909
|
no_text_backslash_linebreak_default,
|
|
701
1910
|
no_trailing_spaces_default,
|
|
702
1911
|
prefer_inline_code_words_default,
|
|
703
1912
|
prefer_link_reference_definitions_default,
|
|
704
|
-
prefer_linked_words_default
|
|
1913
|
+
prefer_linked_words_default,
|
|
1914
|
+
sort_definitions_default
|
|
705
1915
|
];
|
|
706
1916
|
|
|
707
1917
|
//#endregion
|
|
@@ -736,7 +1946,7 @@ __export(meta_exports, {
|
|
|
736
1946
|
version: () => version
|
|
737
1947
|
});
|
|
738
1948
|
const name = "eslint-plugin-markdown-preferences";
|
|
739
|
-
const version = "0.
|
|
1949
|
+
const version = "0.9.0";
|
|
740
1950
|
|
|
741
1951
|
//#endregion
|
|
742
1952
|
//#region src/index.ts
|
|
@@ -745,11 +1955,16 @@ const rules = rules$1.reduce((obj, r) => {
|
|
|
745
1955
|
obj[r.meta.docs.ruleName] = r;
|
|
746
1956
|
return obj;
|
|
747
1957
|
}, {});
|
|
1958
|
+
const resources = {
|
|
1959
|
+
defaultPreserveWords,
|
|
1960
|
+
defaultMinorWords
|
|
1961
|
+
};
|
|
748
1962
|
var src_default = {
|
|
749
1963
|
meta: meta_exports,
|
|
750
1964
|
configs,
|
|
751
|
-
rules
|
|
1965
|
+
rules,
|
|
1966
|
+
resources
|
|
752
1967
|
};
|
|
753
1968
|
|
|
754
1969
|
//#endregion
|
|
755
|
-
export { configs, src_default as default, meta_exports as meta, rules };
|
|
1970
|
+
export { configs, src_default as default, meta_exports as meta, resources, rules };
|