eslint-plugin-markdown-preferences 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -223,6 +223,7 @@ The rules with the following 💄 are included in the `standard` config.
223
223
  | [markdown-preferences/atx-heading-closing-sequence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence-length.html) | enforce consistent length for the closing sequence (trailing #s) in ATX headings. | 🔧 | 💄 |
224
224
  | [markdown-preferences/atx-heading-closing-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/atx-heading-closing-sequence.html) | enforce consistent use of closing sequence in ATX headings. | 🔧 | 💄 |
225
225
  | [markdown-preferences/code-fence-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/code-fence-length.html) | enforce consistent code fence length in fenced code blocks. | 🔧 | 💄 |
226
+ | [markdown-preferences/max-len](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/max-len.html) | enforce maximum length for various Markdown entities | | |
226
227
  | [markdown-preferences/no-laziness-blockquotes](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-laziness-blockquotes.html) | disallow laziness in blockquotes | | ⭐💄 |
227
228
  | [markdown-preferences/ordered-list-marker-sequence](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/ordered-list-marker-sequence.html) | enforce consistent ordered list marker numbering (sequential or flat) | 🔧 | 💄 |
228
229
  | [markdown-preferences/setext-heading-underline-length](https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/setext-heading-underline-length.html) | enforce setext heading underline length | 🔧 | 💄 |
package/lib/index.d.ts CHANGED
@@ -140,6 +140,11 @@ interface RuleOptions {
140
140
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/list-marker-alignment.html
141
141
  */
142
142
  'markdown-preferences/list-marker-alignment'?: Linter.RuleEntry<MarkdownPreferencesListMarkerAlignment>;
143
+ /**
144
+ * enforce maximum length for various Markdown entities
145
+ * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/max-len.html
146
+ */
147
+ 'markdown-preferences/max-len'?: Linter.RuleEntry<MarkdownPreferencesMaxLen>;
143
148
  /**
144
149
  * disallow trailing punctuation in headings.
145
150
  * @see https://ota-meshi.github.io/eslint-plugin-markdown-preferences/rules/no-heading-trailing-punctuation.html
@@ -403,6 +408,59 @@ type MarkdownPreferencesLinkTitleStyle = [] | [{
403
408
  type MarkdownPreferencesListMarkerAlignment = [] | [{
404
409
  align?: ("left" | "right");
405
410
  }];
411
+ type MarkdownPreferencesMaxLen = [] | [{
412
+ heading?: (number | "ignore");
413
+ paragraph?: (number | "ignore");
414
+ table?: (number | "ignore");
415
+ html?: (number | "ignore");
416
+ math?: (number | "ignore");
417
+ code?: ((number | "ignore") | {
418
+ [k: string]: (number | "ignore");
419
+ });
420
+ frontmatter?: ((number | "ignore") | {
421
+ [k: string]: (number | "ignore");
422
+ });
423
+ list?: ((number | "ignore") | {
424
+ heading?: (number | "ignore");
425
+ paragraph?: (number | "ignore");
426
+ table?: (number | "ignore");
427
+ html?: (number | "ignore");
428
+ math?: (number | "ignore");
429
+ code?: ((number | "ignore") | {
430
+ [k: string]: (number | "ignore");
431
+ });
432
+ frontmatter?: ((number | "ignore") | {
433
+ [k: string]: (number | "ignore");
434
+ });
435
+ });
436
+ blockquote?: ((number | "ignore") | {
437
+ heading?: (number | "ignore");
438
+ paragraph?: (number | "ignore");
439
+ table?: (number | "ignore");
440
+ html?: (number | "ignore");
441
+ math?: (number | "ignore");
442
+ code?: ((number | "ignore") | {
443
+ [k: string]: (number | "ignore");
444
+ });
445
+ frontmatter?: ((number | "ignore") | {
446
+ [k: string]: (number | "ignore");
447
+ });
448
+ });
449
+ footnoteDefinition?: ((number | "ignore") | {
450
+ heading?: (number | "ignore");
451
+ paragraph?: (number | "ignore");
452
+ table?: (number | "ignore");
453
+ html?: (number | "ignore");
454
+ math?: (number | "ignore");
455
+ code?: ((number | "ignore") | {
456
+ [k: string]: (number | "ignore");
457
+ });
458
+ frontmatter?: ((number | "ignore") | {
459
+ [k: string]: (number | "ignore");
460
+ });
461
+ });
462
+ ignoreUrls?: boolean;
463
+ }];
406
464
  type MarkdownPreferencesNoHeadingTrailingPunctuation = [] | [{
407
465
  punctuation?: (string | {
408
466
  "1"?: string;
@@ -587,7 +645,7 @@ declare namespace meta_d_exports {
587
645
  export { name, version };
588
646
  }
589
647
  declare const name: "eslint-plugin-markdown-preferences";
590
- declare const version: "0.39.0";
648
+ declare const version: "0.40.1";
591
649
  //#endregion
592
650
  //#region src/language/ast-types.d.ts
593
651
  type Node = mdast.Node;
package/lib/index.js CHANGED
@@ -732,7 +732,7 @@ function getOtherMarker(unavailableMarker) {
732
732
  /**
733
733
  * Parse rule options.
734
734
  */
735
- function parseOptions$8(options) {
735
+ function parseOptions$9(options) {
736
736
  const primary = options.primary || "-";
737
737
  const secondary = options.secondary || getOtherMarker(primary);
738
738
  if (primary === secondary) throw new Error(`\`primary\` and \`secondary\` cannot be the same (primary: "${primary}", secondary: "${secondary}").`);
@@ -800,7 +800,7 @@ var bullet_list_marker_style_default = createRule("bullet-list-marker-style", {
800
800
  },
801
801
  create(context) {
802
802
  const sourceCode = context.sourceCode;
803
- const options = parseOptions$8(context.options[0] || {});
803
+ const options = parseOptions$9(context.options[0] || {});
804
804
  let containerStack = {
805
805
  node: sourceCode.ast,
806
806
  level: 1,
@@ -1418,7 +1418,7 @@ var custom_container_marker_spacing_default = createRule("custom-container-marke
1418
1418
  /**
1419
1419
  * Parse options with defaults.
1420
1420
  */
1421
- function parseOptions$7(options) {
1421
+ function parseOptions$8(options) {
1422
1422
  return {
1423
1423
  linkDefinitionPlacement: {
1424
1424
  referencedFromSingleSection: options?.linkDefinitionPlacement?.referencedFromSingleSection || "document-last",
@@ -1490,7 +1490,7 @@ var definitions_last_default = createRule("definitions-last", {
1490
1490
  },
1491
1491
  create(context) {
1492
1492
  const sourceCode = context.sourceCode;
1493
- const options = parseOptions$7(context.options[0]);
1493
+ const options = parseOptions$8(context.options[0]);
1494
1494
  /**
1495
1495
  * Determine whether a node can be placed as the last node of the document or a section.
1496
1496
  */
@@ -4570,12 +4570,12 @@ var PreserveWordsContext = class {
4570
4570
  if (firstLowerWord !== phrase[0].toLowerCase()) continue;
4571
4571
  while (subWords.length < phrase.length) {
4572
4572
  const word = words.next();
4573
- if (word.done) return null;
4573
+ if (word.done) break;
4574
4574
  subWords.push(word.value);
4575
4575
  }
4576
- if (subWords.every((word, i) => word.toLowerCase() === phrase[i].toLowerCase())) {
4576
+ if (subWords.length >= phrase.length && phrase.every((word, i) => subWords[i].toLowerCase() === word.toLowerCase())) {
4577
4577
  let matchCount = 0;
4578
- for (let i = 0; i < subWords.length; i++) if (subWords[i] === phrase[i]) matchCount++;
4578
+ for (let i = 0; i < phrase.length; i++) if (subWords[i] === phrase[i]) matchCount++;
4579
4579
  if (!returnCandidate || matchCount > returnCandidate.matchCount) returnCandidate = {
4580
4580
  preservePhrase: phrase,
4581
4581
  matchCount
@@ -5464,7 +5464,7 @@ function parseMathClosingSequenceFromText(text) {
5464
5464
  /**
5465
5465
  * Parse options.
5466
5466
  */
5467
- function parseOptions$6(options) {
5467
+ function parseOptions$7(options) {
5468
5468
  const listItems = options?.listItems;
5469
5469
  return { listItems: {
5470
5470
  first: listItems?.first ?? 1,
@@ -5517,7 +5517,7 @@ var indent_default = createRule("indent", {
5517
5517
  },
5518
5518
  create(context) {
5519
5519
  const sourceCode = context.sourceCode;
5520
- const options = parseOptions$6(context.options[0]);
5520
+ const options = parseOptions$7(context.options[0]);
5521
5521
  class AbsBlockStack {
5522
5522
  violations = [];
5523
5523
  getCurrentBlockquote() {
@@ -6723,11 +6723,11 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6723
6723
  },
6724
6724
  create(context) {
6725
6725
  const sourceCode = context.sourceCode;
6726
- const optionProvider = parseOptions$9(context.options[0]);
6726
+ const optionProvider = parseOptions$10(context.options[0]);
6727
6727
  /**
6728
6728
  * Parse the options.
6729
6729
  */
6730
- function parseOptions$9(option) {
6730
+ function parseOptions$10(option) {
6731
6731
  const newline = option?.newline ?? "never";
6732
6732
  const multiline = option?.multiline ?? false;
6733
6733
  return (bracketsRange) => {
@@ -6869,7 +6869,7 @@ var link_bracket_newline_default = createRule("link-bracket-newline", {
6869
6869
  /**
6870
6870
  * The basic option for links and images.
6871
6871
  */
6872
- function parseOptions$5(option) {
6872
+ function parseOptions$6(option) {
6873
6873
  const space = option?.space ?? "never";
6874
6874
  const imagesInLinks = option?.imagesInLinks;
6875
6875
  return {
@@ -6919,7 +6919,7 @@ var link_bracket_spacing_default = createRule("link-bracket-spacing", {
6919
6919
  },
6920
6920
  create(context) {
6921
6921
  const sourceCode = context.sourceCode;
6922
- const options = parseOptions$5(context.options[0]);
6922
+ const options = parseOptions$6(context.options[0]);
6923
6923
  /**
6924
6924
  * Verify the space after the opening bracket and before the closing bracket.
6925
6925
  */
@@ -7228,11 +7228,11 @@ var link_paren_newline_default = createRule("link-paren-newline", {
7228
7228
  },
7229
7229
  create(context) {
7230
7230
  const sourceCode = context.sourceCode;
7231
- const optionProvider = parseOptions$9(context.options[0]);
7231
+ const optionProvider = parseOptions$10(context.options[0]);
7232
7232
  /**
7233
7233
  * Parse the options.
7234
7234
  */
7235
- function parseOptions$9(option) {
7235
+ function parseOptions$10(option) {
7236
7236
  const newline = option?.newline ?? "never";
7237
7237
  const multiline = option?.multiline ?? false;
7238
7238
  return (openingParenIndex, closingParenIndex) => {
@@ -7690,6 +7690,209 @@ var list_marker_alignment_default = createRule("list-marker-alignment", {
7690
7690
  }
7691
7691
  });
7692
7692
 
7693
+ //#endregion
7694
+ //#region src/rules/max-len.ts
7695
+ const URL_PATTERN = /https?:\/\/(?:w{3}\.)?[\w#%+\-.:=@~]{1,256}\.[\w()]{1,6}\b[\w#%&()+\-./:=?@~]*/gu;
7696
+ const maxLengthSchema = { oneOf: [{
7697
+ type: "integer",
7698
+ minimum: 1
7699
+ }, { enum: ["ignore"] }] };
7700
+ const maxLengthParLangSchema = { oneOf: [maxLengthSchema, {
7701
+ type: "object",
7702
+ patternProperties: { ".*": maxLengthSchema },
7703
+ additionalProperties: false
7704
+ }] };
7705
+ const maxLengthParPhrasingNodeOptionsSchema = {
7706
+ type: "object",
7707
+ properties: {
7708
+ heading: maxLengthSchema,
7709
+ paragraph: maxLengthSchema,
7710
+ table: maxLengthSchema,
7711
+ html: maxLengthSchema,
7712
+ math: maxLengthSchema,
7713
+ code: maxLengthParLangSchema,
7714
+ frontmatter: maxLengthParLangSchema
7715
+ },
7716
+ additionalProperties: false
7717
+ };
7718
+ const maxLengthOptionForContainerSchema = { oneOf: [maxLengthSchema, maxLengthParPhrasingNodeOptionsSchema] };
7719
+ /**
7720
+ * Parse options
7721
+ */
7722
+ function parseOptions$5(options, sourceCode) {
7723
+ const ignoreUrls = options.ignoreUrls ?? true;
7724
+ const maxLengthForNode = parseOptionsParNode({
7725
+ heading: options.heading ?? 80,
7726
+ paragraph: options.paragraph ?? 120,
7727
+ table: options.table ?? 120,
7728
+ html: options.html ?? 120,
7729
+ math: options.math ?? 120,
7730
+ code: options.code ?? "ignore",
7731
+ frontmatter: options.frontmatter ?? "ignore"
7732
+ });
7733
+ const maxLengthForInBlockquote = parseContainerOption(options.blockquote ?? {});
7734
+ const maxLengthForInList = parseContainerOption(options.list ?? {});
7735
+ const maxLengthForInFootnoteDefinition = parseContainerOption(options.footnoteDefinition ?? {});
7736
+ return {
7737
+ maxLength: (node) => {
7738
+ let maxLength = null;
7739
+ let parent = getParent(sourceCode, node);
7740
+ while (parent) {
7741
+ const len = parent.type === "blockquote" ? maxLengthForInBlockquote(node) : parent.type === "listItem" ? maxLengthForInList(node) : parent.type === "footnoteDefinition" ? maxLengthForInFootnoteDefinition(node) : null;
7742
+ if (len === "ignore") return "ignore";
7743
+ if (len && (maxLength == null || len < maxLength)) maxLength = len;
7744
+ parent = getParent(sourceCode, parent);
7745
+ }
7746
+ return maxLength ?? maxLengthForNode(node);
7747
+ },
7748
+ ignoreUrls
7749
+ };
7750
+ }
7751
+ /**
7752
+ * Parse code option
7753
+ */
7754
+ function parseCodeOption(option) {
7755
+ if (typeof option === "number" || option === "ignore") return () => option;
7756
+ return (node) => {
7757
+ if (node.lang && option[node.lang] != null) return option[node.lang];
7758
+ return "ignore";
7759
+ };
7760
+ }
7761
+ /**
7762
+ * Parse frontmatter option
7763
+ */
7764
+ function parseFrontmatterOption(option) {
7765
+ if (typeof option === "number" || option === "ignore") return () => option;
7766
+ return (node) => {
7767
+ if (option[node.type] != null) return option[node.type];
7768
+ return "ignore";
7769
+ };
7770
+ }
7771
+ /**
7772
+ * Parse container option
7773
+ */
7774
+ function parseContainerOption(option) {
7775
+ if (typeof option === "number" || option === "ignore") return () => option;
7776
+ return parseOptionsParNode(option);
7777
+ }
7778
+ /**
7779
+ * Parse options for phrasing nodes
7780
+ */
7781
+ function parseOptionsParNode(option) {
7782
+ const headingMax = option.heading;
7783
+ const paragraphMax = option.paragraph;
7784
+ const tableMax = option.table;
7785
+ const htmlMax = option.html;
7786
+ const mathMax = option.math;
7787
+ const maxLengthForCode = option.code ? parseCodeOption(option.code) : null;
7788
+ const maxLengthForFrontmatter = option.frontmatter ? parseFrontmatterOption(option.frontmatter) : null;
7789
+ /**
7790
+ * Get max length for a specific node type
7791
+ */
7792
+ function getMaxLength(node) {
7793
+ switch (node.type) {
7794
+ case "heading": return headingMax ?? null;
7795
+ case "paragraph": return paragraphMax ?? null;
7796
+ case "table": return tableMax ?? null;
7797
+ case "html": return htmlMax ?? null;
7798
+ case "math": return mathMax ?? null;
7799
+ case "code": return maxLengthForCode?.(node) ?? null;
7800
+ case "yaml":
7801
+ case "toml": return maxLengthForFrontmatter?.(node) ?? null;
7802
+ }
7803
+ return null;
7804
+ }
7805
+ return (node) => getMaxLength(node);
7806
+ }
7807
+ var max_len_default = createRule("max-len", {
7808
+ meta: {
7809
+ type: "layout",
7810
+ docs: {
7811
+ description: "enforce maximum length for various Markdown entities",
7812
+ categories: [],
7813
+ listCategory: "Decorative"
7814
+ },
7815
+ fixable: void 0,
7816
+ hasSuggestions: false,
7817
+ schema: [{
7818
+ type: "object",
7819
+ properties: {
7820
+ ...maxLengthParPhrasingNodeOptionsSchema.properties,
7821
+ list: maxLengthOptionForContainerSchema,
7822
+ blockquote: maxLengthOptionForContainerSchema,
7823
+ footnoteDefinition: maxLengthOptionForContainerSchema,
7824
+ ignoreUrls: { type: "boolean" }
7825
+ },
7826
+ additionalProperties: false
7827
+ }],
7828
+ messages: { maxLenExceeded: "Line length of {{actual}} exceeds the maximum of {{max}}." }
7829
+ },
7830
+ create(context) {
7831
+ const sourceCode = context.sourceCode;
7832
+ const options = parseOptions$5(context.options[0] ?? {}, sourceCode);
7833
+ const checkedLines = /* @__PURE__ */ new Set();
7834
+ /**
7835
+ * Check if a line contains a URL (following @stylistic/max-len behavior)
7836
+ * URLs are identified by the URL_PATTERN
7837
+ */
7838
+ function containsUrl(line) {
7839
+ URL_PATTERN.lastIndex = 0;
7840
+ return URL_PATTERN.test(line);
7841
+ }
7842
+ /**
7843
+ * Check lines in a node against a maximum length
7844
+ */
7845
+ function checkLines(node) {
7846
+ const maxLength = options.maxLength(node);
7847
+ if (maxLength === "ignore") return;
7848
+ const nodeLoc = sourceCode.getLoc(node);
7849
+ const startLine = nodeLoc.start.line;
7850
+ const endLine = nodeLoc.end.line;
7851
+ for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) {
7852
+ if (checkedLines.has(lineNumber)) continue;
7853
+ const line = sourceCode.lines[lineNumber - 1];
7854
+ const width = getTextWidth(line);
7855
+ if (width > maxLength) {
7856
+ if (options.ignoreUrls && containsUrl(line)) continue;
7857
+ context.report({
7858
+ node,
7859
+ loc: {
7860
+ start: {
7861
+ line: lineNumber,
7862
+ column: 1
7863
+ },
7864
+ end: {
7865
+ line: lineNumber,
7866
+ column: line.length + 1
7867
+ }
7868
+ },
7869
+ messageId: "maxLenExceeded",
7870
+ data: {
7871
+ actual: String(width),
7872
+ max: String(maxLength)
7873
+ }
7874
+ });
7875
+ checkedLines.add(lineNumber);
7876
+ }
7877
+ }
7878
+ }
7879
+ return {
7880
+ heading: checkLines,
7881
+ paragraph: checkLines,
7882
+ table: checkLines,
7883
+ html(node) {
7884
+ const parent = getParent(sourceCode, node);
7885
+ if (parent.type !== "root" && parent.type !== "blockquote" && parent.type !== "listItem" && parent.type !== "footnoteDefinition" && parent.type !== "customContainer") return;
7886
+ checkLines(node);
7887
+ },
7888
+ code: checkLines,
7889
+ yaml: checkLines,
7890
+ toml: checkLines,
7891
+ math: checkLines
7892
+ };
7893
+ }
7894
+ });
7895
+
7693
7896
  //#endregion
7694
7897
  //#region src/rules/no-heading-trailing-punctuation.ts
7695
7898
  const DEFAULT_PUNCTUATION = ".,;:!。、,;:!。、";
@@ -12416,6 +12619,7 @@ const rules$1 = [
12416
12619
  link_paren_spacing_default,
12417
12620
  link_title_style_default,
12418
12621
  list_marker_alignment_default,
12622
+ max_len_default,
12419
12623
  no_heading_trailing_punctuation_default,
12420
12624
  no_implicit_block_closing_default,
12421
12625
  no_laziness_blockquotes_default,
@@ -12550,7 +12754,7 @@ var meta_exports = /* @__PURE__ */ __export({
12550
12754
  version: () => version
12551
12755
  });
12552
12756
  const name = "eslint-plugin-markdown-preferences";
12553
- const version = "0.39.0";
12757
+ const version = "0.40.1";
12554
12758
 
12555
12759
  //#endregion
12556
12760
  //#region src/language/extensions/micromark-custom-container.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-markdown-preferences",
3
- "version": "0.39.0",
3
+ "version": "0.40.1",
4
4
  "description": "ESLint plugin that enforces our markdown preferences",
5
5
  "type": "module",
6
6
  "exports": {