eslint-plugin-markdown-preferences 0.8.0 → 0.10.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 +12 -4
- package/lib/index.d.ts +46 -2
- package/lib/index.js +1226 -83
- 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", {
|
|
@@ -111,6 +203,1108 @@ var hard_linebreak_style_default = createRule("hard-linebreak-style", {
|
|
|
111
203
|
}
|
|
112
204
|
});
|
|
113
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
|
+
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region src/utils/lines.ts
|
|
983
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
984
|
+
var ParsedLines = class {
|
|
985
|
+
lines;
|
|
986
|
+
constructor(codeText) {
|
|
987
|
+
let offset = 0;
|
|
988
|
+
this.lines = codeText.split(/(?<=\n)/u).map((lineText, index) => {
|
|
989
|
+
const start = offset;
|
|
990
|
+
offset += lineText.length;
|
|
991
|
+
const range = [start, offset];
|
|
992
|
+
let text = lineText;
|
|
993
|
+
let linebreak = "";
|
|
994
|
+
if (text.at(-1) === "\n") {
|
|
995
|
+
text = text.slice(0, -1);
|
|
996
|
+
linebreak = "\n";
|
|
997
|
+
}
|
|
998
|
+
if (text.at(-1) === "\r") {
|
|
999
|
+
text = text.slice(0, -1);
|
|
1000
|
+
linebreak = `\r${linebreak}`;
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
text,
|
|
1004
|
+
range,
|
|
1005
|
+
line: index + 1,
|
|
1006
|
+
linebreak
|
|
1007
|
+
};
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
[Symbol.iterator]() {
|
|
1011
|
+
return this.lines[Symbol.iterator]();
|
|
1012
|
+
}
|
|
1013
|
+
get length() {
|
|
1014
|
+
return this.lines.length;
|
|
1015
|
+
}
|
|
1016
|
+
get(lineNumber) {
|
|
1017
|
+
return this.lines[lineNumber - 1];
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
/**
|
|
1021
|
+
* Parse the lines of the source code.
|
|
1022
|
+
* @param sourceCode source code to parse
|
|
1023
|
+
* @returns parsed lines
|
|
1024
|
+
*/
|
|
1025
|
+
function parseLines(sourceCode) {
|
|
1026
|
+
const cached = cache.get(sourceCode);
|
|
1027
|
+
if (cached) return cached;
|
|
1028
|
+
const parsedLines = new ParsedLines(sourceCode.text);
|
|
1029
|
+
cache.set(sourceCode, parsedLines);
|
|
1030
|
+
return parsedLines;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region src/rules/no-laziness-blockquotes.ts
|
|
1035
|
+
/**
|
|
1036
|
+
* Helper function to get blockquote line information.
|
|
1037
|
+
*/
|
|
1038
|
+
function getBlockquoteLineInfo(line) {
|
|
1039
|
+
const regex = /^\s*(?:>\s*)*/u;
|
|
1040
|
+
const match = regex.exec(line.text);
|
|
1041
|
+
return {
|
|
1042
|
+
line,
|
|
1043
|
+
prefix: match[0],
|
|
1044
|
+
level: (match[0].match(/>/gu) || []).length
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
var no_laziness_blockquotes_default = createRule("no-laziness-blockquotes", {
|
|
1048
|
+
meta: {
|
|
1049
|
+
type: "problem",
|
|
1050
|
+
docs: {
|
|
1051
|
+
description: "disallow laziness in blockquotes",
|
|
1052
|
+
categories: ["recommended"],
|
|
1053
|
+
listCategory: "Stylistic"
|
|
1054
|
+
},
|
|
1055
|
+
fixable: void 0,
|
|
1056
|
+
hasSuggestions: true,
|
|
1057
|
+
schema: [],
|
|
1058
|
+
messages: {
|
|
1059
|
+
lazyBlockquoteLine: "Expected {{level}} '>' marker(s), but found {{actualLevel}}. This line will be interpreted as part of level {{level}} blockquote.",
|
|
1060
|
+
addMarker: "Add {{missingMarkers}} '>' marker(s).",
|
|
1061
|
+
addLineBreak: "Add line break to separate from blockquote."
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
create(context) {
|
|
1065
|
+
const sourceCode = context.sourceCode;
|
|
1066
|
+
const checkedLines = /* @__PURE__ */ new Set();
|
|
1067
|
+
const lines = parseLines(sourceCode);
|
|
1068
|
+
/**
|
|
1069
|
+
* Report invalid blockquote lines.
|
|
1070
|
+
*/
|
|
1071
|
+
function reportInvalidLines(invalidLines, base) {
|
|
1072
|
+
const invalidGroups = [];
|
|
1073
|
+
for (const invalidLine of invalidLines) {
|
|
1074
|
+
const currentGroup = invalidGroups.at(-1);
|
|
1075
|
+
if (!currentGroup || currentGroup.level < invalidLine.level) {
|
|
1076
|
+
invalidGroups.push({
|
|
1077
|
+
level: invalidLine.level,
|
|
1078
|
+
lines: [invalidLine]
|
|
1079
|
+
});
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (currentGroup.level === invalidLine.level) {
|
|
1083
|
+
currentGroup.lines.push(invalidLine);
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (invalidLine.level < currentGroup.level) break;
|
|
1087
|
+
}
|
|
1088
|
+
for (const group of invalidGroups) {
|
|
1089
|
+
const first = group.lines[0];
|
|
1090
|
+
const last = group.lines.at(-1);
|
|
1091
|
+
context.report({
|
|
1092
|
+
loc: {
|
|
1093
|
+
start: {
|
|
1094
|
+
line: first.line.line,
|
|
1095
|
+
column: 1
|
|
1096
|
+
},
|
|
1097
|
+
end: {
|
|
1098
|
+
line: last.line.line,
|
|
1099
|
+
column: last.line.text.length + 1
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
messageId: "lazyBlockquoteLine",
|
|
1103
|
+
data: {
|
|
1104
|
+
level: `${base.level}`,
|
|
1105
|
+
actualLevel: `${group.level}`
|
|
1106
|
+
},
|
|
1107
|
+
suggest: [{
|
|
1108
|
+
messageId: "addMarker",
|
|
1109
|
+
data: { missingMarkers: `${base.level - group.level}` },
|
|
1110
|
+
*fix(fixer) {
|
|
1111
|
+
for (const invalidLine of group.lines) yield fixer.replaceTextRange([invalidLine.line.range[0], invalidLine.line.range[0] + invalidLine.prefix.length], base.prefix);
|
|
1112
|
+
}
|
|
1113
|
+
}, {
|
|
1114
|
+
messageId: "addLineBreak",
|
|
1115
|
+
fix: (fixer) => {
|
|
1116
|
+
return fixer.insertTextBeforeRange([first.line.range[0], first.line.range[0]], `${first.prefix.trimEnd()}\n`);
|
|
1117
|
+
}
|
|
1118
|
+
}]
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return { "blockquote:exit"(node) {
|
|
1123
|
+
const loc = sourceCode.getLoc(node);
|
|
1124
|
+
const startLine = loc.start.line;
|
|
1125
|
+
const endLine = loc.end.line;
|
|
1126
|
+
const base = getBlockquoteLineInfo(lines.get(startLine));
|
|
1127
|
+
const invalidLines = [];
|
|
1128
|
+
for (let lineNumber = startLine + 1; lineNumber <= endLine; lineNumber++) {
|
|
1129
|
+
if (checkedLines.has(lineNumber)) {
|
|
1130
|
+
reportInvalidLines(invalidLines, base);
|
|
1131
|
+
invalidLines.length = 0;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
checkedLines.add(lineNumber);
|
|
1135
|
+
const line = lines.get(lineNumber);
|
|
1136
|
+
const current = getBlockquoteLineInfo(line);
|
|
1137
|
+
if (base.level <= current.level) {
|
|
1138
|
+
reportInvalidLines(invalidLines, base);
|
|
1139
|
+
invalidLines.length = 0;
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
invalidLines.push(current);
|
|
1143
|
+
}
|
|
1144
|
+
reportInvalidLines(invalidLines, base);
|
|
1145
|
+
} };
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region src/rules/no-multiple-empty-lines.ts
|
|
1151
|
+
var no_multiple_empty_lines_default = createRule("no-multiple-empty-lines", {
|
|
1152
|
+
meta: {
|
|
1153
|
+
type: "layout",
|
|
1154
|
+
docs: {
|
|
1155
|
+
description: "disallow multiple empty lines in Markdown files.",
|
|
1156
|
+
categories: null,
|
|
1157
|
+
listCategory: "Stylistic"
|
|
1158
|
+
},
|
|
1159
|
+
fixable: "whitespace",
|
|
1160
|
+
hasSuggestions: false,
|
|
1161
|
+
schema: [{
|
|
1162
|
+
type: "object",
|
|
1163
|
+
properties: {
|
|
1164
|
+
max: {
|
|
1165
|
+
type: "integer",
|
|
1166
|
+
minimum: 0,
|
|
1167
|
+
default: 1
|
|
1168
|
+
},
|
|
1169
|
+
maxEOF: {
|
|
1170
|
+
type: "integer",
|
|
1171
|
+
minimum: 0,
|
|
1172
|
+
default: 0
|
|
1173
|
+
},
|
|
1174
|
+
maxBOF: {
|
|
1175
|
+
type: "integer",
|
|
1176
|
+
minimum: 0,
|
|
1177
|
+
default: 0
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
additionalProperties: false
|
|
1181
|
+
}],
|
|
1182
|
+
messages: {
|
|
1183
|
+
blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.",
|
|
1184
|
+
blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.",
|
|
1185
|
+
consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed."
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
create(context) {
|
|
1189
|
+
const sourceCode = context.sourceCode;
|
|
1190
|
+
const option = context.options[0] || {};
|
|
1191
|
+
const max = typeof option.max === "number" ? option.max : 1;
|
|
1192
|
+
const maxEOF = typeof option.maxEOF === "number" ? option.maxEOF : 0;
|
|
1193
|
+
const maxBOF = typeof option.maxBOF === "number" ? option.maxBOF : 0;
|
|
1194
|
+
const ignoreLocs = [];
|
|
1195
|
+
/**
|
|
1196
|
+
* Register the range of nodes to ignore (code, html, yaml, toml, json)
|
|
1197
|
+
* @param node mdast node
|
|
1198
|
+
*/
|
|
1199
|
+
function addIgnoreLoc(node) {
|
|
1200
|
+
const loc = sourceCode.getLoc(node);
|
|
1201
|
+
ignoreLocs.push({
|
|
1202
|
+
startLine: loc.start.line,
|
|
1203
|
+
endLine: loc.end.line
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
return {
|
|
1207
|
+
code: addIgnoreLoc,
|
|
1208
|
+
html: addIgnoreLoc,
|
|
1209
|
+
yaml: addIgnoreLoc,
|
|
1210
|
+
toml: addIgnoreLoc,
|
|
1211
|
+
json: addIgnoreLoc,
|
|
1212
|
+
"root:exit"() {
|
|
1213
|
+
const lines = [...parseLines(sourceCode)];
|
|
1214
|
+
const bofEmptyLines = [];
|
|
1215
|
+
while (lines.length) {
|
|
1216
|
+
if (lines[0].text.trim()) break;
|
|
1217
|
+
bofEmptyLines.push(lines.shift());
|
|
1218
|
+
}
|
|
1219
|
+
const invalidBOFEmptyLines = bofEmptyLines.slice(maxBOF);
|
|
1220
|
+
if (invalidBOFEmptyLines.length > 0) {
|
|
1221
|
+
const first = invalidBOFEmptyLines[0];
|
|
1222
|
+
const last = invalidBOFEmptyLines[invalidBOFEmptyLines.length - 1];
|
|
1223
|
+
context.report({
|
|
1224
|
+
loc: {
|
|
1225
|
+
start: {
|
|
1226
|
+
line: first.line,
|
|
1227
|
+
column: 1
|
|
1228
|
+
},
|
|
1229
|
+
end: {
|
|
1230
|
+
line: last.line,
|
|
1231
|
+
column: last.text.length + 1
|
|
1232
|
+
}
|
|
1233
|
+
},
|
|
1234
|
+
messageId: "blankBeginningOfFile",
|
|
1235
|
+
data: { max: maxBOF },
|
|
1236
|
+
fix(fixer) {
|
|
1237
|
+
return fixer.removeRange([first.range[0], last.range[1]]);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
const eofEmptyLines = [];
|
|
1242
|
+
while (lines.length) {
|
|
1243
|
+
if (lines[lines.length - 1].text.trim()) break;
|
|
1244
|
+
eofEmptyLines.unshift(lines.pop());
|
|
1245
|
+
}
|
|
1246
|
+
const invalidEOFEmptyLines = eofEmptyLines.slice(maxEOF);
|
|
1247
|
+
if (invalidEOFEmptyLines.length > 0) {
|
|
1248
|
+
const first = invalidEOFEmptyLines[0];
|
|
1249
|
+
const last = invalidEOFEmptyLines[invalidEOFEmptyLines.length - 1];
|
|
1250
|
+
context.report({
|
|
1251
|
+
loc: {
|
|
1252
|
+
start: {
|
|
1253
|
+
line: first.line,
|
|
1254
|
+
column: 1
|
|
1255
|
+
},
|
|
1256
|
+
end: {
|
|
1257
|
+
line: last.line,
|
|
1258
|
+
column: last.text.length + 1
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
messageId: "blankEndOfFile",
|
|
1262
|
+
data: { max: maxEOF },
|
|
1263
|
+
fix(fixer) {
|
|
1264
|
+
return fixer.removeRange([first.range[0], last.range[1]]);
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
const emptyLines = [];
|
|
1269
|
+
for (const lineInfo of lines) {
|
|
1270
|
+
if (!lineInfo.text.trim() && !ignoreLocs.some(({ startLine, endLine }) => {
|
|
1271
|
+
return lineInfo.line > startLine && lineInfo.line < endLine;
|
|
1272
|
+
})) {
|
|
1273
|
+
emptyLines.push(lineInfo);
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
const invalidEmptyLines = emptyLines.slice(max);
|
|
1277
|
+
emptyLines.length = 0;
|
|
1278
|
+
if (invalidEmptyLines.length) {
|
|
1279
|
+
const first = invalidEmptyLines[0];
|
|
1280
|
+
const last = invalidEmptyLines[invalidEmptyLines.length - 1];
|
|
1281
|
+
context.report({
|
|
1282
|
+
loc: {
|
|
1283
|
+
start: {
|
|
1284
|
+
line: first.line,
|
|
1285
|
+
column: 1
|
|
1286
|
+
},
|
|
1287
|
+
end: {
|
|
1288
|
+
line: last.line,
|
|
1289
|
+
column: last.text.length + 1
|
|
1290
|
+
}
|
|
1291
|
+
},
|
|
1292
|
+
messageId: "consecutiveBlank",
|
|
1293
|
+
data: {
|
|
1294
|
+
max,
|
|
1295
|
+
pluralizedLines: max === 1 ? "line" : "lines"
|
|
1296
|
+
},
|
|
1297
|
+
fix(fixer) {
|
|
1298
|
+
return fixer.removeRange([first.range[0], last.range[1]]);
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
|
|
114
1308
|
//#endregion
|
|
115
1309
|
//#region src/rules/no-text-backslash-linebreak.ts
|
|
116
1310
|
var no_text_backslash_linebreak_default = createRule("no-text-backslash-linebreak", {
|
|
@@ -242,45 +1436,28 @@ var no_trailing_spaces_default = createRule("no-trailing-spaces", {
|
|
|
242
1436
|
"root:exit"() {
|
|
243
1437
|
const re = /[^\S\n\r]+$/u;
|
|
244
1438
|
const skipMatch = /^[^\S\n\r]*$/u;
|
|
245
|
-
const lines = sourceCode
|
|
246
|
-
const linebreaks = sourceCode.text.match(/\r?\n/gu);
|
|
1439
|
+
const lines = parseLines(sourceCode);
|
|
247
1440
|
const commentLineNumbers = getCommentLineNumbers();
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1;
|
|
252
|
-
const lineLength = lines[i].length + linebreakLength;
|
|
253
|
-
const matches = re.exec(lines[i]);
|
|
254
|
-
if (!matches) {
|
|
255
|
-
totalLength += lineLength;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
1441
|
+
for (const lineInfo of lines) {
|
|
1442
|
+
const matches = re.exec(lineInfo.text);
|
|
1443
|
+
if (!matches) continue;
|
|
258
1444
|
const location = {
|
|
259
1445
|
start: {
|
|
260
|
-
line:
|
|
1446
|
+
line: lineInfo.line,
|
|
261
1447
|
column: matches.index + 1
|
|
262
1448
|
},
|
|
263
1449
|
end: {
|
|
264
|
-
line:
|
|
265
|
-
column:
|
|
1450
|
+
line: lineInfo.line,
|
|
1451
|
+
column: matches.index + 1 + matches[0].length
|
|
266
1452
|
}
|
|
267
1453
|
};
|
|
268
|
-
const
|
|
269
|
-
const rangeEnd = totalLength + location.end.column - 1;
|
|
1454
|
+
const range = [lineInfo.range[0] + location.start.column - 1, lineInfo.range[0] + location.end.column - 1];
|
|
270
1455
|
if (ignoreNodes.some((node) => {
|
|
271
|
-
const
|
|
272
|
-
return
|
|
273
|
-
}))
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
if (skipBlankLines && skipMatch.test(lines[i])) {
|
|
278
|
-
totalLength += lineLength;
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const fixRange = [rangeStart, rangeEnd];
|
|
282
|
-
if (!ignoreComments || !commentLineNumbers.has(lineNumber)) report(location, fixRange);
|
|
283
|
-
totalLength += lineLength;
|
|
1456
|
+
const nodeRange = sourceCode.getRange(node);
|
|
1457
|
+
return nodeRange[0] <= range[0] && range[1] <= nodeRange[1];
|
|
1458
|
+
})) continue;
|
|
1459
|
+
if (skipBlankLines && skipMatch.test(lineInfo.text)) continue;
|
|
1460
|
+
if (!ignoreComments || !commentLineNumbers.has(lineInfo.line)) report(location, range);
|
|
284
1461
|
}
|
|
285
1462
|
}
|
|
286
1463
|
};
|
|
@@ -621,25 +1798,6 @@ var prefer_link_reference_definitions_default = createRule("prefer-link-referenc
|
|
|
621
1798
|
}
|
|
622
1799
|
});
|
|
623
1800
|
|
|
624
|
-
//#endregion
|
|
625
|
-
//#region src/utils/url.ts
|
|
626
|
-
/**
|
|
627
|
-
* Utility function to check if a string is a valid URL.
|
|
628
|
-
*/
|
|
629
|
-
function isValidURL(url) {
|
|
630
|
-
return Boolean(createURLSafe(url));
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Utility function to create a URL object safely.
|
|
634
|
-
*/
|
|
635
|
-
function createURLSafe(url) {
|
|
636
|
-
try {
|
|
637
|
-
return new URL(url);
|
|
638
|
-
} catch {
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
1801
|
//#endregion
|
|
644
1802
|
//#region src/rules/prefer-linked-words.ts
|
|
645
1803
|
var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
@@ -741,7 +1899,7 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
741
1899
|
* Adjust link to be relative to the file.
|
|
742
1900
|
*/
|
|
743
1901
|
function adjustLink(link) {
|
|
744
|
-
if (
|
|
1902
|
+
if (URL.canParse(link)) return link;
|
|
745
1903
|
if (link.startsWith("#")) return link;
|
|
746
1904
|
const absoluteLink = path.isAbsolute(link) || path.posix.isAbsolute(link) ? link : path.join(context.cwd, link);
|
|
747
1905
|
return `./${path.relative(path.dirname(context.filename), absoluteLink)}`;
|
|
@@ -749,31 +1907,6 @@ var prefer_linked_words_default = createRule("prefer-linked-words", {
|
|
|
749
1907
|
}
|
|
750
1908
|
});
|
|
751
1909
|
|
|
752
|
-
//#endregion
|
|
753
|
-
//#region src/utils/regexp.ts
|
|
754
|
-
const RE_REGEXP_STR = /^\/(.+)\/([A-Za-z]*)$/u;
|
|
755
|
-
/**
|
|
756
|
-
* Convert a string to the `RegExp`.
|
|
757
|
-
* Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`.
|
|
758
|
-
* Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`.
|
|
759
|
-
*
|
|
760
|
-
* @param {string} string The string to convert.
|
|
761
|
-
* @returns {RegExp} Returns the `RegExp`.
|
|
762
|
-
*/
|
|
763
|
-
function toRegExp(string) {
|
|
764
|
-
const parts = RE_REGEXP_STR.exec(string);
|
|
765
|
-
if (parts) return new RegExp(parts[1], parts[2]);
|
|
766
|
-
return { test: (s) => s === string };
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Checks whether given string is regexp string
|
|
770
|
-
* @param {string} string
|
|
771
|
-
* @returns {boolean}
|
|
772
|
-
*/
|
|
773
|
-
function isRegExp(string) {
|
|
774
|
-
return Boolean(RE_REGEXP_STR.test(string));
|
|
775
|
-
}
|
|
776
|
-
|
|
777
1910
|
//#endregion
|
|
778
1911
|
//#region src/rules/sort-definitions.ts
|
|
779
1912
|
var sort_definitions_default = createRule("sort-definitions", {
|
|
@@ -947,14 +2080,14 @@ var sort_definitions_default = createRule("sort-definitions", {
|
|
|
947
2080
|
}
|
|
948
2081
|
/** Compile order option */
|
|
949
2082
|
function compileOption(orderOption) {
|
|
950
|
-
const cache = /* @__PURE__ */ new Map();
|
|
2083
|
+
const cache$1 = /* @__PURE__ */ new Map();
|
|
951
2084
|
const compiled = compileOptionWithoutCache(orderOption);
|
|
952
2085
|
return {
|
|
953
2086
|
match: (node) => {
|
|
954
|
-
const cached = cache.get(node);
|
|
2087
|
+
const cached = cache$1.get(node);
|
|
955
2088
|
if (cached != null) return cached;
|
|
956
2089
|
const result = compiled.match(node);
|
|
957
|
-
cache.set(node, result);
|
|
2090
|
+
cache$1.set(node, result);
|
|
958
2091
|
return result;
|
|
959
2092
|
},
|
|
960
2093
|
sort: compiled.sort
|
|
@@ -1005,7 +2138,7 @@ var sort_definitions_default = createRule("sort-definitions", {
|
|
|
1005
2138
|
if (node.label === patternStr || node.identifier === patternStr) return true;
|
|
1006
2139
|
if (node.type === "definition") {
|
|
1007
2140
|
if (node.url === patternStr) return true;
|
|
1008
|
-
if (
|
|
2141
|
+
if (URL.canParse(patternStr) && URL.canParse(node.url)) {
|
|
1009
2142
|
const normalizedPattern = normalizedURL(patternStr);
|
|
1010
2143
|
const normalizedUrl = normalizedURL(node.url);
|
|
1011
2144
|
if (normalizedUrl.startsWith(normalizedPattern)) return true;
|
|
@@ -1052,7 +2185,7 @@ function getQuote(text) {
|
|
|
1052
2185
|
* Normalize a URL by ensuring it ends with a slash.
|
|
1053
2186
|
*/
|
|
1054
2187
|
function normalizedURL(url) {
|
|
1055
|
-
const urlObj =
|
|
2188
|
+
const urlObj = new URL(url);
|
|
1056
2189
|
if (!urlObj) return url;
|
|
1057
2190
|
return urlObj.href.endsWith("/") ? urlObj.href : `${urlObj.href}/`;
|
|
1058
2191
|
}
|
|
@@ -1060,8 +2193,12 @@ function normalizedURL(url) {
|
|
|
1060
2193
|
//#endregion
|
|
1061
2194
|
//#region src/utils/rules.ts
|
|
1062
2195
|
const rules$1 = [
|
|
2196
|
+
canonical_code_block_language_default,
|
|
1063
2197
|
definitions_last_default,
|
|
1064
2198
|
hard_linebreak_style_default,
|
|
2199
|
+
heading_casing_default,
|
|
2200
|
+
no_laziness_blockquotes_default,
|
|
2201
|
+
no_multiple_empty_lines_default,
|
|
1065
2202
|
no_text_backslash_linebreak_default,
|
|
1066
2203
|
no_trailing_spaces_default,
|
|
1067
2204
|
prefer_inline_code_words_default,
|
|
@@ -1091,6 +2228,7 @@ const plugins = {
|
|
|
1091
2228
|
};
|
|
1092
2229
|
const rules$2 = {
|
|
1093
2230
|
"markdown-preferences/hard-linebreak-style": "error",
|
|
2231
|
+
"markdown-preferences/no-laziness-blockquotes": "error",
|
|
1094
2232
|
"markdown-preferences/no-text-backslash-linebreak": "error"
|
|
1095
2233
|
};
|
|
1096
2234
|
|
|
@@ -1102,7 +2240,7 @@ __export(meta_exports, {
|
|
|
1102
2240
|
version: () => version
|
|
1103
2241
|
});
|
|
1104
2242
|
const name = "eslint-plugin-markdown-preferences";
|
|
1105
|
-
const version = "0.
|
|
2243
|
+
const version = "0.10.0";
|
|
1106
2244
|
|
|
1107
2245
|
//#endregion
|
|
1108
2246
|
//#region src/index.ts
|
|
@@ -1111,11 +2249,16 @@ const rules = rules$1.reduce((obj, r) => {
|
|
|
1111
2249
|
obj[r.meta.docs.ruleName] = r;
|
|
1112
2250
|
return obj;
|
|
1113
2251
|
}, {});
|
|
2252
|
+
const resources = {
|
|
2253
|
+
defaultPreserveWords,
|
|
2254
|
+
defaultMinorWords
|
|
2255
|
+
};
|
|
1114
2256
|
var src_default = {
|
|
1115
2257
|
meta: meta_exports,
|
|
1116
2258
|
configs,
|
|
1117
|
-
rules
|
|
2259
|
+
rules,
|
|
2260
|
+
resources
|
|
1118
2261
|
};
|
|
1119
2262
|
|
|
1120
2263
|
//#endregion
|
|
1121
|
-
export { configs, src_default as default, meta_exports as meta, rules };
|
|
2264
|
+
export { configs, src_default as default, meta_exports as meta, resources, rules };
|