Refactor to remove micromark helper matchAndGetTokensByType.

This commit is contained in:
David Anson 2024-09-18 21:02:59 -07:00
parent 2ea3f95fd1
commit 87daa5b06c
4 changed files with 66 additions and 184 deletions

View file

@ -483,60 +483,33 @@ function getReferenceLinkImageData(tokens) {
case "image": case "image":
case "link": case "link":
{ {
let isShortcut = false; // Identify if shortcut or full/collapsed
let isFullOrCollapsed = false; let isShortcut = (token.children.length === 1);
let labelText = null; const isFullOrCollapsed = (token.children.length === 2) && !token.children.some((t) => t.type === "resource");
let referenceStringText = null; const [ labelText ] = micromark.getDescendantsByType(token, [ "label", "labelText" ]);
const shortcutCandidate = const [ referenceString ] = micromark.getDescendantsByType(token, [ "reference", "referenceString" ]);
micromark.matchAndGetTokensByType(token.children, [ "label" ]); let label = labelText?.text;
if (shortcutCandidate) { // Identify if footnote
labelText = if (!isShortcut && !isFullOrCollapsed) {
micromark.getTokenTextByType( const [ footnoteCallMarker, footnoteCallString ] = token.children.filter(
shortcutCandidate[0].children, "labelText" (t) => [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ].includes(t.type)
);
isShortcut = (labelText !== null);
}
const fullAndCollapsedCandidate =
micromark.matchAndGetTokensByType(
token.children, [ "label", "reference" ]
); );
if (fullAndCollapsedCandidate) { if (footnoteCallMarker && footnoteCallString) {
labelText = label = `${footnoteCallMarker.text}${footnoteCallString.text}`;
micromark.getTokenTextByType( isShortcut = true;
fullAndCollapsedCandidate[0].children, "labelText" }
);
referenceStringText =
micromark.getTokenTextByType(
fullAndCollapsedCandidate[1].children, "referenceString"
);
isFullOrCollapsed = (labelText !== null);
} }
const footnote = micromark.matchAndGetTokensByType( // Track link (handle shortcuts separately due to ambiguity in "text [text] text")
token.children,
[
"gfmFootnoteCallLabelMarker", "gfmFootnoteCallMarker",
"gfmFootnoteCallString", "gfmFootnoteCallLabelMarker"
],
[ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ]
);
if (footnote) {
const callMarkerText = footnote[0].text;
const callString = footnote[1].text;
labelText = `${callMarkerText}${callString}`;
isShortcut = true;
}
// Track shortcuts separately due to ambiguity in "text [text] text"
if (isShortcut || isFullOrCollapsed) { if (isShortcut || isFullOrCollapsed) {
const referenceDatum = [ const referenceDatum = [
token.startLine - 1, token.startLine - 1,
token.startColumn - 1, token.startColumn - 1,
token.text.length, token.text.length,
// @ts-ignore label.length,
labelText.length, (referenceString?.text || "").length
(referenceStringText || "").length
]; ];
const reference = const reference =
normalizeReference(referenceStringText || labelText); normalizeReference(referenceString?.text || label);
const dictionary = isShortcut ? shortcuts : references; const dictionary = isShortcut ? shortcuts : references;
const referenceData = dictionary.get(reference) || []; const referenceData = dictionary.get(reference) || [];
referenceData.push(referenceDatum); referenceData.push(referenceDatum);
@ -1360,31 +1333,6 @@ function getTokenTextByType(tokens, type) {
return null; return null;
} }
/**
* Determines a list of Micromark tokens matches and returns a subset.
*
* @param {Token[]} tokens Micromark tokens.
* @param {TokenType[]} matchTypes Types to match.
* @param {TokenType[]} [resultTypes] Types to return.
* @returns {Token[] | null} Matching tokens.
*/
function matchAndGetTokensByType(tokens, matchTypes, resultTypes) {
if (tokens.length !== matchTypes.length) {
return null;
}
resultTypes = resultTypes || matchTypes;
const result = [];
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < matchTypes.length; i++) {
if (tokens[i].type !== matchTypes[i]) {
return null;
} else if (resultTypes.includes(matchTypes[i])) {
result.push(tokens[i]);
}
}
return result;
}
/** /**
* Set containing token types that do not contain content. * Set containing token types that do not contain content.
* *
@ -1416,7 +1364,6 @@ module.exports = {
getTokenTextByType, getTokenTextByType,
inHtmlFlow, inHtmlFlow,
isHtmlFlowComment, isHtmlFlowComment,
matchAndGetTokensByType,
nonContentTokens nonContentTokens
}; };
@ -5273,15 +5220,15 @@ module.exports = {
const { addErrorContext, allPunctuation } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"); const { addErrorContext, allPunctuation } = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js");
const { matchAndGetTokensByType } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"); const { getDescendantsByType } = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs");
const { filterByTypesCached } = __webpack_require__(/*! ./cache */ "../lib/cache.js"); const { filterByTypesCached } = __webpack_require__(/*! ./cache */ "../lib/cache.js");
/** @typedef {import("../helpers/micromark.cjs").TokenType} TokenType */ /** @typedef {import("../helpers/micromark.cjs").TokenType} TokenType */
/** @type {Map<TokenType, TokenType[]>} */ /** @type {TokenType[][]} */
const emphasisAndChildrenTypes = new Map([ const emphasisTypes = [
[ "emphasis", [ "emphasisSequence", "emphasisText", "emphasisSequence" ] ], [ "emphasis", "emphasisText" ],
[ "strong", [ "strongSequence", "strongText", "strongSequence" ] ] [ "strong", "strongText" ]
]); ];
// eslint-disable-next-line jsdoc/valid-types // eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */ /** @type import("./markdownlint").Rule */
@ -5299,21 +5246,15 @@ module.exports = {
.filter((token) => .filter((token) =>
(token.parent?.type === "content") && !token.parent?.parent && (token.children.length === 1) (token.parent?.type === "content") && !token.parent?.parent && (token.children.length === 1)
); );
for (const paragraphToken of paragraphTokens) { for (const emphasisType of emphasisTypes) {
const childToken = paragraphToken.children[0]; const textTokens = getDescendantsByType(paragraphTokens, emphasisType);
for (const [ emphasisType, emphasisChildrenTypes ] of emphasisAndChildrenTypes) { for (const textToken of textTokens) {
if (childToken.type === emphasisType) { if (
const matchingTokens = matchAndGetTokensByType(childToken.children, emphasisChildrenTypes); (textToken.children.length === 1) &&
if (matchingTokens) { (textToken.children[0].type === "data") &&
const textToken = matchingTokens[1]; !punctuationRe.test(textToken.text)
if ( ) {
(textToken.children.length === 1) && addErrorContext(onError, textToken.startLine, textToken.text);
(textToken.children[0].type === "data") &&
!punctuationRe.test(textToken.text)
) {
addErrorContext(onError, textToken.startLine, textToken.text);
}
}
} }
} }
} }

View file

@ -471,60 +471,33 @@ function getReferenceLinkImageData(tokens) {
case "image": case "image":
case "link": case "link":
{ {
let isShortcut = false; // Identify if shortcut or full/collapsed
let isFullOrCollapsed = false; let isShortcut = (token.children.length === 1);
let labelText = null; const isFullOrCollapsed = (token.children.length === 2) && !token.children.some((t) => t.type === "resource");
let referenceStringText = null; const [ labelText ] = micromark.getDescendantsByType(token, [ "label", "labelText" ]);
const shortcutCandidate = const [ referenceString ] = micromark.getDescendantsByType(token, [ "reference", "referenceString" ]);
micromark.matchAndGetTokensByType(token.children, [ "label" ]); let label = labelText?.text;
if (shortcutCandidate) { // Identify if footnote
labelText = if (!isShortcut && !isFullOrCollapsed) {
micromark.getTokenTextByType( const [ footnoteCallMarker, footnoteCallString ] = token.children.filter(
shortcutCandidate[0].children, "labelText" (t) => [ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ].includes(t.type)
);
isShortcut = (labelText !== null);
}
const fullAndCollapsedCandidate =
micromark.matchAndGetTokensByType(
token.children, [ "label", "reference" ]
); );
if (fullAndCollapsedCandidate) { if (footnoteCallMarker && footnoteCallString) {
labelText = label = `${footnoteCallMarker.text}${footnoteCallString.text}`;
micromark.getTokenTextByType( isShortcut = true;
fullAndCollapsedCandidate[0].children, "labelText" }
);
referenceStringText =
micromark.getTokenTextByType(
fullAndCollapsedCandidate[1].children, "referenceString"
);
isFullOrCollapsed = (labelText !== null);
} }
const footnote = micromark.matchAndGetTokensByType( // Track link (handle shortcuts separately due to ambiguity in "text [text] text")
token.children,
[
"gfmFootnoteCallLabelMarker", "gfmFootnoteCallMarker",
"gfmFootnoteCallString", "gfmFootnoteCallLabelMarker"
],
[ "gfmFootnoteCallMarker", "gfmFootnoteCallString" ]
);
if (footnote) {
const callMarkerText = footnote[0].text;
const callString = footnote[1].text;
labelText = `${callMarkerText}${callString}`;
isShortcut = true;
}
// Track shortcuts separately due to ambiguity in "text [text] text"
if (isShortcut || isFullOrCollapsed) { if (isShortcut || isFullOrCollapsed) {
const referenceDatum = [ const referenceDatum = [
token.startLine - 1, token.startLine - 1,
token.startColumn - 1, token.startColumn - 1,
token.text.length, token.text.length,
// @ts-ignore label.length,
labelText.length, (referenceString?.text || "").length
(referenceStringText || "").length
]; ];
const reference = const reference =
normalizeReference(referenceStringText || labelText); normalizeReference(referenceString?.text || label);
const dictionary = isShortcut ? shortcuts : references; const dictionary = isShortcut ? shortcuts : references;
const referenceData = dictionary.get(reference) || []; const referenceData = dictionary.get(reference) || [];
referenceData.push(referenceDatum); referenceData.push(referenceDatum);

View file

@ -475,31 +475,6 @@ function getTokenTextByType(tokens, type) {
return null; return null;
} }
/**
* Determines a list of Micromark tokens matches and returns a subset.
*
* @param {Token[]} tokens Micromark tokens.
* @param {TokenType[]} matchTypes Types to match.
* @param {TokenType[]} [resultTypes] Types to return.
* @returns {Token[] | null} Matching tokens.
*/
function matchAndGetTokensByType(tokens, matchTypes, resultTypes) {
if (tokens.length !== matchTypes.length) {
return null;
}
resultTypes = resultTypes || matchTypes;
const result = [];
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < matchTypes.length; i++) {
if (tokens[i].type !== matchTypes[i]) {
return null;
} else if (resultTypes.includes(matchTypes[i])) {
result.push(tokens[i]);
}
}
return result;
}
/** /**
* Set containing token types that do not contain content. * Set containing token types that do not contain content.
* *
@ -531,6 +506,5 @@ module.exports = {
getTokenTextByType, getTokenTextByType,
inHtmlFlow, inHtmlFlow,
isHtmlFlowComment, isHtmlFlowComment,
matchAndGetTokensByType,
nonContentTokens nonContentTokens
}; };

View file

@ -3,15 +3,15 @@
"use strict"; "use strict";
const { addErrorContext, allPunctuation } = require("../helpers"); const { addErrorContext, allPunctuation } = require("../helpers");
const { matchAndGetTokensByType } = require("../helpers/micromark.cjs"); const { getDescendantsByType } = require("../helpers/micromark.cjs");
const { filterByTypesCached } = require("./cache"); const { filterByTypesCached } = require("./cache");
/** @typedef {import("../helpers/micromark.cjs").TokenType} TokenType */ /** @typedef {import("../helpers/micromark.cjs").TokenType} TokenType */
/** @type {Map<TokenType, TokenType[]>} */ /** @type {TokenType[][]} */
const emphasisAndChildrenTypes = new Map([ const emphasisTypes = [
[ "emphasis", [ "emphasisSequence", "emphasisText", "emphasisSequence" ] ], [ "emphasis", "emphasisText" ],
[ "strong", [ "strongSequence", "strongText", "strongSequence" ] ] [ "strong", "strongText" ]
]); ];
// eslint-disable-next-line jsdoc/valid-types // eslint-disable-next-line jsdoc/valid-types
/** @type import("./markdownlint").Rule */ /** @type import("./markdownlint").Rule */
@ -29,21 +29,15 @@ module.exports = {
.filter((token) => .filter((token) =>
(token.parent?.type === "content") && !token.parent?.parent && (token.children.length === 1) (token.parent?.type === "content") && !token.parent?.parent && (token.children.length === 1)
); );
for (const paragraphToken of paragraphTokens) { for (const emphasisType of emphasisTypes) {
const childToken = paragraphToken.children[0]; const textTokens = getDescendantsByType(paragraphTokens, emphasisType);
for (const [ emphasisType, emphasisChildrenTypes ] of emphasisAndChildrenTypes) { for (const textToken of textTokens) {
if (childToken.type === emphasisType) { if (
const matchingTokens = matchAndGetTokensByType(childToken.children, emphasisChildrenTypes); (textToken.children.length === 1) &&
if (matchingTokens) { (textToken.children[0].type === "data") &&
const textToken = matchingTokens[1]; !punctuationRe.test(textToken.text)
if ( ) {
(textToken.children.length === 1) && addErrorContext(onError, textToken.startLine, textToken.text);
(textToken.children[0].type === "data") &&
!punctuationRe.test(textToken.text)
) {
addErrorContext(onError, textToken.startLine, textToken.text);
}
}
} }
} }
} }