markdownlint 0.27.0 → 0.28.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.
@@ -34,7 +34,7 @@ module.exports = {
34
34
  "information": new URL("https://example.com/rules/any-blockquote"),
35
35
  "tags": [ "test" ],
36
36
  "function": function rule(params, onError) {
37
- params.tokens.filter(function filterToken(token) {
37
+ params.parsers.markdownit.tokens.filter(function filterToken(token) {
38
38
  return token.type === "blockquote_open";
39
39
  }).forEach(function forToken(blockquote) {
40
40
  var lines = blockquote.map[1] - blockquote.map[0];
@@ -120,8 +120,6 @@ exception.
120
120
  - [Code for all `markdownlint` built-in rules][lib]
121
121
  - [Package configuration for publishing to npm][test-rules-npm]
122
122
  - Packages should export a single rule object or an `Array` of rule objects
123
- - [Custom rules from the Microsoft/vscode-docs-authoring
124
- repository][vscode-docs-authoring]
125
123
  - [Custom rules from the axibase/docs-util repository][docs-util]
126
124
  - [Custom rules from the webhintio/hint repository][hint]
127
125
 
@@ -374,4 +372,3 @@ Yields the `params` object:
374
372
  [options-custom-rules]: ../README.md#optionscustomrules
375
373
  [test-rules]: ../test/rules
376
374
  [test-rules-npm]: ../test/rules/npm
377
- [vscode-docs-authoring]: https://github.com/microsoft/vscode-docs-authoring/tree/main/packages/docs-linting/markdownlint-custom-rules
package/doc/Rules.md CHANGED
@@ -1410,42 +1410,48 @@ Aliases: `no-bare-urls`
1410
1410
 
1411
1411
  Fixable: Some violations can be fixed by tooling
1412
1412
 
1413
- This rule is triggered whenever a URL is given that isn't surrounded by angle
1414
- brackets:
1413
+ This rule is triggered whenever a URL or email address appears without
1414
+ surrounding angle brackets:
1415
1415
 
1416
1416
  ```markdown
1417
- For more information, see https://www.example.com/.
1417
+ For more info, visit https://www.example.com/ or email user@example.com.
1418
1418
  ```
1419
1419
 
1420
- To fix this, add angle brackets around the URL:
1420
+ To fix this, add angle brackets around the URL or email address:
1421
1421
 
1422
1422
  ```markdown
1423
- For more information, see <https://www.example.com/>.
1423
+ For more info, visit <https://www.example.com/> or email <user@example.com>.
1424
1424
  ```
1425
1425
 
1426
- Note: To use a bare URL without it being converted into a link, enclose it in
1427
- a code block, otherwise in some Markdown parsers it *will* be converted:
1426
+ Note: To include a bare URL or email without it being converted into a link,
1427
+ wrap it in a code span:
1428
1428
 
1429
1429
  ```markdown
1430
- `https://www.example.com`
1430
+ Not a clickable link: `https://www.example.com`
1431
1431
  ```
1432
1432
 
1433
- Note: The following scenario does *not* trigger this rule to avoid conflicts
1434
- with `MD011`/`no-reversed-links`:
1433
+ Note: The following scenario does not trigger this rule because it could be a
1434
+ shortcut link:
1435
1435
 
1436
1436
  ```markdown
1437
1437
  [https://www.example.com]
1438
1438
  ```
1439
1439
 
1440
- The use of quotes around a bare link will *not* trigger this rule, either:
1440
+ Note: The following syntax triggers this rule because the nested link could be
1441
+ a shortcut link (which takes precedence):
1441
1442
 
1442
1443
  ```markdown
1443
- "https://www.example.com"
1444
- 'https://www.example.com'
1444
+ [text [shortcut] text](https://example.com)
1445
1445
  ```
1446
1446
 
1447
- Rationale: Without angle brackets, the URL isn't converted into a link by many
1448
- Markdown parsers.
1447
+ To avoid this, escape both inner brackets:
1448
+
1449
+ ```markdown
1450
+ [link \[text\] link](https://example.com)
1451
+ ```
1452
+
1453
+ Rationale: Without angle brackets, a bare URL or email isn't converted into a
1454
+ link by some Markdown parsers.
1449
1455
 
1450
1456
  <a name="md035"></a>
1451
1457
 
@@ -2184,7 +2190,7 @@ convert heading to lowercase, remove punctuation, convert spaces to dashes,
2184
2190
  append an incrementing integer as needed for uniqueness.
2185
2191
 
2186
2192
  [github-section-links]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
2187
- [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/main/lib/html/pipeline/toc_filter.rb
2193
+ [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/f13a1534cb650ba17af400d1acd3a22c28004c09/lib/html/pipeline/toc_filter.rb
2188
2194
 
2189
2195
  <a name="md052"></a>
2190
2196
 
package/doc/md034.md CHANGED
@@ -6,39 +6,45 @@ Aliases: `no-bare-urls`
6
6
 
7
7
  Fixable: Some violations can be fixed by tooling
8
8
 
9
- This rule is triggered whenever a URL is given that isn't surrounded by angle
10
- brackets:
9
+ This rule is triggered whenever a URL or email address appears without
10
+ surrounding angle brackets:
11
11
 
12
12
  ```markdown
13
- For more information, see https://www.example.com/.
13
+ For more info, visit https://www.example.com/ or email user@example.com.
14
14
  ```
15
15
 
16
- To fix this, add angle brackets around the URL:
16
+ To fix this, add angle brackets around the URL or email address:
17
17
 
18
18
  ```markdown
19
- For more information, see <https://www.example.com/>.
19
+ For more info, visit <https://www.example.com/> or email <user@example.com>.
20
20
  ```
21
21
 
22
- Note: To use a bare URL without it being converted into a link, enclose it in
23
- a code block, otherwise in some Markdown parsers it *will* be converted:
22
+ Note: To include a bare URL or email without it being converted into a link,
23
+ wrap it in a code span:
24
24
 
25
25
  ```markdown
26
- `https://www.example.com`
26
+ Not a clickable link: `https://www.example.com`
27
27
  ```
28
28
 
29
- Note: The following scenario does *not* trigger this rule to avoid conflicts
30
- with `MD011`/`no-reversed-links`:
29
+ Note: The following scenario does not trigger this rule because it could be a
30
+ shortcut link:
31
31
 
32
32
  ```markdown
33
33
  [https://www.example.com]
34
34
  ```
35
35
 
36
- The use of quotes around a bare link will *not* trigger this rule, either:
36
+ Note: The following syntax triggers this rule because the nested link could be
37
+ a shortcut link (which takes precedence):
37
38
 
38
39
  ```markdown
39
- "https://www.example.com"
40
- 'https://www.example.com'
40
+ [text [shortcut] text](https://example.com)
41
41
  ```
42
42
 
43
- Rationale: Without angle brackets, the URL isn't converted into a link by many
44
- Markdown parsers.
43
+ To avoid this, escape both inner brackets:
44
+
45
+ ```markdown
46
+ [link \[text\] link](https://example.com)
47
+ ```
48
+
49
+ Rationale: Without angle brackets, a bare URL or email isn't converted into a
50
+ link by some Markdown parsers.
package/doc/md051.md CHANGED
@@ -47,4 +47,4 @@ convert heading to lowercase, remove punctuation, convert spaces to dashes,
47
47
  append an incrementing integer as needed for uniqueness.
48
48
 
49
49
  [github-section-links]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links
50
- [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/main/lib/html/pipeline/toc_filter.rb
50
+ [github-heading-algorithm]: https://github.com/gjtorikian/html-pipeline/blob/f13a1534cb650ba17af400d1acd3a22c28004c09/lib/html/pipeline/toc_filter.rb
package/helpers/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2022 David Anson
3
+ Copyright (c) David Anson
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -2,6 +2,8 @@
2
2
 
3
3
  "use strict";
4
4
 
5
+ const micromark = require("./micromark.cjs");
6
+
5
7
  // Regular expression for matching common newline characters
6
8
  // See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js
7
9
  const newLineRe = /\r\n?|\n/g;
@@ -33,10 +35,6 @@ const emphasisMarkersRe = /[_*]/g;
33
35
  const blockquotePrefixRe = /^[>\s]*/;
34
36
  module.exports.blockquotePrefixRe = blockquotePrefixRe;
35
37
 
36
- // Regular expression for reference links (full, collapsed, and shortcut)
37
- const referenceLinkRe =
38
- /!?\\?\[((?:\[[^\]\0]*\]|[^[\]\0])*)\](?:\[([^\]\0]*)\]|([^(])|$)/g;
39
-
40
38
  // Regular expression for link reference definitions
41
39
  const linkReferenceDefinitionRe = /^ {0,3}\[([^\]]*[^\\])\]:/;
42
40
  module.exports.linkReferenceDefinitionRe = linkReferenceDefinitionRe;
@@ -193,19 +191,6 @@ module.exports.escapeForRegExp = function escapeForRegExp(str) {
193
191
  return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
194
192
  };
195
193
 
196
- // Un-escapes Markdown content (simple algorithm; not a parser)
197
- const escapedMarkdownRe = /\\./g;
198
- module.exports.unescapeMarkdown =
199
- function unescapeMarkdown(markdown, replacement) {
200
- return markdown.replace(escapedMarkdownRe, (match) => {
201
- const char = match[1];
202
- if ("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".includes(char)) {
203
- return replacement || char;
204
- }
205
- return match;
206
- });
207
- };
208
-
209
194
  /**
210
195
  * Return the string representation of a fence markup character.
211
196
  *
@@ -288,7 +273,7 @@ module.exports.unorderedListStyleFor = function unorderedListStyleFor(token) {
288
273
  * @returns {void}
289
274
  */
290
275
  function filterTokens(params, type, handler) {
291
- for (const token of params.tokens) {
276
+ for (const token of params.parsers.markdownit.tokens) {
292
277
  if (token.type === type) {
293
278
  handler(token);
294
279
  }
@@ -343,7 +328,7 @@ module.exports.getLineMetadata = function getLineMetadata(params) {
343
328
  filterTokens(params, "hr", (token) => {
344
329
  lineMetadata[token.map[0]][6] = true;
345
330
  });
346
- for (const token of params.tokens.filter(isMathBlock)) {
331
+ for (const token of params.parsers.markdownit.tokens.filter(isMathBlock)) {
347
332
  for (let i = token.map[0]; i < token.map[1]; i++) {
348
333
  lineMetadata[i][7] = true;
349
334
  }
@@ -437,7 +422,7 @@ module.exports.forEachInlineChild = forEachInlineChild;
437
422
  // Calls the provided function for each heading's content
438
423
  module.exports.forEachHeading = function forEachHeading(params, handler) {
439
424
  let heading = null;
440
- for (const token of params.tokens) {
425
+ for (const token of params.parsers.markdownit.tokens) {
441
426
  if (token.type === "heading_open") {
442
427
  heading = token;
443
428
  } else if (token.type === "heading_close") {
@@ -818,137 +803,117 @@ module.exports.emphasisMarkersInContent = emphasisMarkersInContent;
818
803
  /**
819
804
  * Returns an object with information about reference links and images.
820
805
  *
821
- * @param {Object} lineMetadata Line metadata object.
806
+ * @param {Object} params RuleParams instance.
822
807
  * @returns {Object} Reference link/image data.
823
808
  */
824
- function getReferenceLinkImageData(lineMetadata) {
825
- // Initialize return values
826
- const references = new Map();
827
- const shortcuts = new Map();
809
+ function getReferenceLinkImageData(params) {
810
+ const normalizeReference = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
828
811
  const definitions = new Map();
829
- const duplicateDefinitions = [];
830
812
  const definitionLineIndices = [];
831
- // Define helper functions
832
- const normalizeLabel = (s) => s.toLowerCase().trim().replace(/\s+/g, " ");
833
- const exclusions = [];
834
- const excluded = (match) => withinAnyRange(
835
- exclusions, 0, match.index, match[0].length - (match[3] || "").length
836
- );
837
- // Convert input to single-line so multi-line links/images are easier
838
- const lineOffsets = [];
839
- let currentOffset = 0;
840
- const contentLines = [];
841
- forEachLine(lineMetadata, (line, lineIndex, inCode) => {
842
- lineOffsets[lineIndex] = currentOffset;
843
- if (!inCode) {
844
- line = line.replace(blockquotePrefixRe, "");
845
- if (line.trim().length === 0) {
846
- // Allow RegExp to detect the end of a block
847
- line = "\0";
848
- }
849
- contentLines.push(line);
850
- currentOffset += line.length + 1;
851
- }
852
- });
853
- lineOffsets.push(currentOffset);
854
- const contentLine = contentLines.join(" ");
855
- // Determine single-line exclusions for inline code spans
856
- forEachInlineCodeSpan(contentLine, (code, lineIndex, columnIndex) => {
857
- exclusions.push([ 0, columnIndex, code.length ]);
858
- });
859
- // Identify all link/image reference definitions
860
- forEachLine(lineMetadata, (line, lineIndex, inCode) => {
861
- if (!inCode) {
862
- const linkReferenceDefinitionMatch = linkReferenceDefinitionRe.exec(line);
863
- if (linkReferenceDefinitionMatch) {
864
- const label = normalizeLabel(linkReferenceDefinitionMatch[1]);
865
- if (definitions.has(label)) {
866
- duplicateDefinitions.push([ label, lineIndex ]);
867
- } else {
868
- definitions.set(label, lineIndex);
869
- }
870
- const labelLength = linkReferenceDefinitionMatch[0].length;
871
- exclusions.push([ 0, lineOffsets[lineIndex], labelLength ]);
872
- const hasDefinition = line.slice(labelLength).trim().length > 0;
873
- definitionLineIndices.push(lineIndex);
874
- if (!hasDefinition) {
875
- definitionLineIndices.push(lineIndex + 1);
813
+ const duplicateDefinitions = [];
814
+ const references = new Map();
815
+ const shortcuts = new Map();
816
+ const filteredTokens =
817
+ micromark.filterByTypes(
818
+ params.parsers.micromark.tokens,
819
+ [
820
+ // definitionLineIndices
821
+ "definition", "gfmFootnoteDefinition",
822
+ // definitions and definitionLineIndices
823
+ "definitionLabelString", "gfmFootnoteDefinitionLabelString",
824
+ // references and shortcuts
825
+ "gfmFootnoteCall", "image", "link"
826
+ ]
827
+ );
828
+ for (const token of filteredTokens) {
829
+ let labelPrefix = "";
830
+ // eslint-disable-next-line default-case
831
+ switch (token.type) {
832
+ case "definition":
833
+ case "gfmFootnoteDefinition":
834
+ // definitionLineIndices
835
+ for (let i = token.startLine; i <= token.endLine; i++) {
836
+ definitionLineIndices.push(i - 1);
876
837
  }
877
- }
878
- }
879
- });
880
- // Identify all link and image references
881
- let lineIndex = 0;
882
- const pendingContents = [
883
- {
884
- "content": contentLine,
885
- "contentLineIndex": 0,
886
- "contentIndex": 0,
887
- "topLevel": true
888
- }
889
- ];
890
- let pendingContent = null;
891
- while ((pendingContent = pendingContents.shift())) {
892
- const { content, contentLineIndex, contentIndex, topLevel } =
893
- pendingContent;
894
- let referenceLinkMatch = null;
895
- while ((referenceLinkMatch = referenceLinkRe.exec(content)) !== null) {
896
- const [ matchString, matchText, matchLabel ] = referenceLinkMatch;
897
- if (
898
- !matchString.startsWith("\\") &&
899
- !matchString.startsWith("!\\") &&
900
- !matchText.endsWith("\\") &&
901
- !(matchLabel || "").endsWith("\\") &&
902
- !(topLevel && excluded(referenceLinkMatch))
903
- ) {
904
- const shortcutLink = (matchLabel === undefined);
905
- const collapsedLink =
906
- (!shortcutLink && (matchLabel.length === 0));
907
- const label = normalizeLabel(
908
- (shortcutLink || collapsedLink) ? matchText : matchLabel
909
- );
910
- if (label.length > 0) {
911
- const referenceindex = referenceLinkMatch.index;
912
- if (topLevel) {
913
- // Calculate line index
914
- while (lineOffsets[lineIndex + 1] <= referenceindex) {
915
- lineIndex++;
916
- }
838
+ break;
839
+ case "gfmFootnoteDefinitionLabelString":
840
+ labelPrefix = "^";
841
+ case "definitionLabelString": // eslint-disable-line no-fallthrough
842
+ {
843
+ // definitions and definitionLineIndices
844
+ const reference = normalizeReference(`${labelPrefix}${token.text}`);
845
+ if (definitions.has(reference)) {
846
+ duplicateDefinitions.push([ reference, token.startLine - 1 ]);
917
847
  } else {
918
- // Use provided line index
919
- lineIndex = contentLineIndex;
848
+ definitions.set(reference, token.startLine - 1);
920
849
  }
921
- const referenceIndex = referenceindex +
922
- (topLevel ? -lineOffsets[lineIndex] : contentIndex);
923
- const referenceDatum = [
924
- lineIndex,
925
- referenceIndex,
926
- matchString.length,
927
- matchText.length,
928
- (matchLabel || "").length
929
- ];
930
- if (shortcutLink) {
931
- // Track separately due to ambiguity in "text [text] text"
932
- const shortcutData = shortcuts.get(label) || [];
933
- shortcutData.push(referenceDatum);
934
- shortcuts.set(label, shortcutData);
935
- } else {
936
- // Track reference and location
937
- const referenceData = references.get(label) || [];
938
- referenceData.push(referenceDatum);
939
- references.set(label, referenceData);
850
+ }
851
+ break;
852
+ case "gfmFootnoteCall":
853
+ case "image":
854
+ case "link":
855
+ {
856
+ let isShortcut = false;
857
+ let isFullOrCollapsed = false;
858
+ let labelText = null;
859
+ let referenceStringText = null;
860
+ const shortcutCandidate =
861
+ micromark.matchAndGetTokensByType(token.children, [ "label" ]);
862
+ if (shortcutCandidate) {
863
+ labelText =
864
+ micromark.getTokenTextByType(
865
+ shortcutCandidate[0].children, "labelText"
866
+ );
867
+ isShortcut = (labelText !== null);
940
868
  }
941
- // Check for links embedded in brackets
942
- if (!matchString.startsWith("!")) {
943
- pendingContents.push({
944
- "content": matchText,
945
- "contentLineIndex": lineIndex,
946
- "contentIndex": referenceIndex + 1,
947
- "topLevel": false
948
- });
869
+ const fullAndCollapsedCandidate =
870
+ micromark.matchAndGetTokensByType(
871
+ token.children, [ "label", "reference" ]
872
+ );
873
+ if (fullAndCollapsedCandidate) {
874
+ labelText =
875
+ micromark.getTokenTextByType(
876
+ fullAndCollapsedCandidate[0].children, "labelText"
877
+ );
878
+ referenceStringText =
879
+ micromark.getTokenTextByType(
880
+ fullAndCollapsedCandidate[1].children, "referenceString"
881
+ );
882
+ isFullOrCollapsed = (labelText !== null);
883
+ }
884
+ const footnote = micromark.matchAndGetTokensByType(
885
+ token.children,
886
+ [
887
+ "gfmFootnoteCallLabelMarker", "gfmFootnoteCallMarker",
888
+ "gfmFootnoteCallString", "gfmFootnoteCallLabelMarker"
889
+ ],
890
+ [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ]
891
+ );
892
+ if (footnote) {
893
+ const callMarkerText = footnote[0].text;
894
+ const callString = footnote[1].text;
895
+ labelText = `${callMarkerText}${callString}`;
896
+ isShortcut = true;
897
+ }
898
+ // Track shortcuts separately due to ambiguity in "text [text] text"
899
+ if (isShortcut || isFullOrCollapsed) {
900
+ const referenceDatum = [
901
+ token.startLine - 1,
902
+ token.startColumn - 1,
903
+ token.text.length,
904
+ // @ts-ignore
905
+ labelText.length,
906
+ (referenceStringText || "").length
907
+ ];
908
+ const reference =
909
+ normalizeReference(referenceStringText || labelText);
910
+ const dictionary = isShortcut ? shortcuts : references;
911
+ const referenceData = dictionary.get(reference) || [];
912
+ referenceData.push(referenceDatum);
913
+ dictionary.set(reference, referenceData);
949
914
  }
950
915
  }
951
- }
916
+ break;
952
917
  }
953
918
  }
954
919
  return {
@@ -1187,110 +1152,3 @@ function expandTildePath(file, os) {
1187
1152
  return homedir ? file.replace(/^~($|\/|\\)/, `${homedir}$1`) : file;
1188
1153
  }
1189
1154
  module.exports.expandTildePath = expandTildePath;
1190
-
1191
- /**
1192
- * RegExp.exec-style implementation of function expressions.
1193
- *
1194
- * @param {Function} funcExp Function that takes string and returns
1195
- * [index, length] or null.
1196
- * @param {string} input String to search.
1197
- * @returns {string[] | null} RegExp.exec-style [match] with an index property.
1198
- */
1199
- function funcExpExec(funcExp, input) {
1200
- // Start or resume match
1201
- // @ts-ignore
1202
- const lastIndex = funcExp.lastIndex || 0;
1203
- const result = funcExp(input.slice(lastIndex));
1204
- if (result) {
1205
- // Update lastIndex and return match
1206
- const [ subIndex, length ] = result;
1207
- const index = lastIndex + subIndex;
1208
- // @ts-ignore
1209
- funcExp.lastIndex = index + length;
1210
- const match = [ input.slice(index, index + length) ];
1211
- // @ts-ignore
1212
- match.index = index;
1213
- return match;
1214
- }
1215
- // Reset lastIndex and return no match
1216
- // @ts-ignore
1217
- funcExp.lastIndex = 0;
1218
- return null;
1219
- }
1220
- module.exports.funcExpExec = funcExpExec;
1221
-
1222
- const urlFeProtocolRe = /(?:http|ftp)s?:\/\//i;
1223
- const urlFeAutolinkTerminalsRe = / |$/;
1224
- const urlFeBareTerminalsRe = /[ ,!`'"\]]|$/;
1225
- const urlFeNonTerminalsRe = "-#/";
1226
- const urlFePunctuationRe = /\p{Punctuation}/u;
1227
- const urlFePrefixToPostfix = new Map([
1228
- [ " ", " " ],
1229
- [ "`", "`" ],
1230
- [ "'", "'" ],
1231
- [ "\"", "\"" ],
1232
- [ "‘", "’" ],
1233
- [ "“", "”" ],
1234
- [ "«", "»" ],
1235
- [ "*", "*" ],
1236
- [ "_", "_" ],
1237
- [ "(", ")" ],
1238
- [ "[", "]" ],
1239
- [ "{", "}" ],
1240
- [ "<", ">" ],
1241
- [ ">", "<" ]
1242
- ]);
1243
-
1244
- /**
1245
- * Function expression that matches URLs.
1246
- *
1247
- * @param {string} input Substring to search for a URL.
1248
- * @returns {Array | null} [index, length] of URL or null.
1249
- */
1250
- function urlFe(input) {
1251
- // Find start of URL by searching for protocol
1252
- const match = input.match(urlFeProtocolRe);
1253
- if (match) {
1254
- // Look for matching pre/postfix characters (ex: <...>)
1255
- const start = match.index || 0;
1256
- const length = match[0].length;
1257
- const prefix = input[start - 1] || " ";
1258
- const postfix = urlFePrefixToPostfix.get(prefix);
1259
- // @ts-ignore
1260
- let endPostfix = input.indexOf(postfix, start + length);
1261
- if (endPostfix === -1) {
1262
- endPostfix = input.length;
1263
- }
1264
- // Look for characters that terminate a URL
1265
- const terminalsRe =
1266
- (prefix === "<") ? urlFeAutolinkTerminalsRe : urlFeBareTerminalsRe;
1267
- const endTerminal = start + input.slice(start).search(terminalsRe);
1268
- // Determine tentative end of URL
1269
- let end = Math.min(endPostfix, endTerminal);
1270
- if (prefix === " ") {
1271
- // If the URL used " " as pre/postfix characters, trim the end
1272
- if (input[end - 1] === ")") {
1273
- // Trim any ")" beyond the last "(...)" pair
1274
- const lastOpenParen = input.lastIndexOf("(", end - 2);
1275
- if (lastOpenParen <= start) {
1276
- end--;
1277
- } else {
1278
- const nextCloseParen = input.indexOf(")", lastOpenParen + 1);
1279
- end = nextCloseParen + 1;
1280
- }
1281
- } else {
1282
- // Trim unwanted punctuation
1283
- while (
1284
- !urlFeNonTerminalsRe.includes(input[end - 1]) &&
1285
- urlFePunctuationRe.test(input[end - 1])
1286
- ) {
1287
- end--;
1288
- }
1289
- }
1290
- }
1291
- return [ start, end - start ];
1292
- }
1293
- // No match
1294
- return null;
1295
- }
1296
- module.exports.urlFe = urlFe;