Reimplement MD049/emphasis-style, MD050/strong-style to use micromark parser (with smaller ranges and handling of multi-line fixes).

This commit is contained in:
David Anson 2023-05-25 03:47:34 +00:00
parent e1233aad4b
commit 7005a8a438
9 changed files with 1211 additions and 348 deletions

View file

@ -1165,61 +1165,6 @@ function applyFixes(input, errors) {
} }
module.exports.applyFixes = applyFixes; module.exports.applyFixes = applyFixes;
/**
* Gets the range and fixInfo values for reporting an error if the expected
* text is found on the specified line.
*
* @param {string[]} lines Lines of Markdown content.
* @param {number} lineIndex Line index to check.
* @param {string} search Text to search for.
* @param {string} replace Text to replace with.
* @param {number} [instance] Instance on the line (1-based).
* @returns {Object} Range and fixInfo wrapper.
*/
module.exports.getRangeAndFixInfoIfFound = function (lines, lineIndex, search, replace) {
var instance = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
var range = null;
var fixInfo = null;
var searchIndex = -1;
while (instance > 0) {
searchIndex = lines[lineIndex].indexOf(search, searchIndex + 1);
instance--;
}
if (searchIndex !== -1) {
var column = searchIndex + 1;
var length = search.length;
range = [column, length];
fixInfo = {
"editColumn": column,
"deleteCount": length,
"insertText": replace
};
}
return {
range: range,
fixInfo: fixInfo
};
};
/**
* Gets the next (subsequent) child token if it is of the expected type.
*
* @param {Object} parentToken Parent token.
* @param {Object} childToken Child token basis.
* @param {string} nextType Token type of next token.
* @param {string} nextNextType Token type of next-next token.
* @returns {Object} Next token.
*/
function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
var children = parentToken.children;
var index = children.indexOf(childToken);
if (index !== -1 && children.length > index + 2 && children[index + 1].type === nextType && children[index + 2].type === nextNextType) {
return children[index + 1];
}
return null;
}
module.exports.getNextChildToken = getNextChildToken;
/** /**
* Expands a path with a tilde to an absolute path. * Expands a path with a tilde to an absolute path.
* *
@ -1569,6 +1514,17 @@ function matchAndGetTokensByType(tokens, matchTypes, resultTypes) {
} }
return result; return result;
} }
/**
* Returns the specified token iff it is of the desired type.
*
* @param {Token} token Micromark token candidate.
* @param {string} type Desired type.
* @returns {Token | null} Token instance.
*/
function tokenIfType(token, type) {
return token && token.type === type ? token : null;
}
module.exports = { module.exports = {
"parse": micromarkParse, "parse": micromarkParse,
filterByPredicate: filterByPredicate, filterByPredicate: filterByPredicate,
@ -1576,7 +1532,8 @@ module.exports = {
getHtmlTagInfo: getHtmlTagInfo, getHtmlTagInfo: getHtmlTagInfo,
getMicromarkEvents: getMicromarkEvents, getMicromarkEvents: getMicromarkEvents,
getTokenTextByType: getTokenTextByType, getTokenTextByType: getTokenTextByType,
matchAndGetTokensByType: matchAndGetTokensByType matchAndGetTokensByType: matchAndGetTokensByType,
tokenIfType: tokenIfType
}; };
/***/ }), /***/ }),
@ -5387,7 +5344,8 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
addErrorContext = _require.addErrorContext; addErrorContext = _require.addErrorContext;
var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"), var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
filterByTypes = _require2.filterByTypes; filterByTypes = _require2.filterByTypes,
tokenIfType = _require2.tokenIfType;
var leftSpaceRe = /^\s(?:[^`]|$)/; var leftSpaceRe = /^\s(?:[^`]|$)/;
var rightSpaceRe = /[^`]\s$/; var rightSpaceRe = /[^`]\s$/;
var trimCodeText = function trimCodeText(text, start, end) { var trimCodeText = function trimCodeText(text, start, end) {
@ -5400,9 +5358,6 @@ var trimCodeText = function trimCodeText(text, start, end) {
} }
return text; return text;
}; };
var tokenIfType = function tokenIfType(token, type) {
return token && token.type === type && token;
};
module.exports = { module.exports = {
"names": ["MD038", "no-space-in-code"], "names": ["MD038", "no-space-in-code"],
"description": "Spaces inside code span elements", "description": "Spaces inside code span elements",
@ -6086,49 +6041,56 @@ module.exports = {
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"), var _require = __webpack_require__(/*! ../helpers */ "../helpers/helpers.js"),
addError = _require.addError, addError = _require.addError,
emphasisOrStrongStyleFor = _require.emphasisOrStrongStyleFor, emphasisOrStrongStyleFor = _require.emphasisOrStrongStyleFor;
forEachInlineChild = _require.forEachInlineChild, var _require2 = __webpack_require__(/*! ../helpers/micromark.cjs */ "../helpers/micromark.cjs"),
getNextChildToken = _require.getNextChildToken, filterByTypes = _require2.filterByTypes,
getRangeAndFixInfoIfFound = _require.getRangeAndFixInfoIfFound; tokenIfType = _require2.tokenIfType;
var impl = function impl(params, onError, tagPrefix, asterisk, underline) { var impl = function impl(params, onError, type, asterisk, underline) {
var style = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : "consistent"; var style = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : "consistent";
var lastLineNumber = -1; var emphasisTokens = filterByTypes(params.parsers.micromark.tokens, [type]);
var instances = new Map(); var _iterator = _createForOfIteratorHelper(emphasisTokens),
forEachInlineChild(params, "".concat(tagPrefix, "_open"), function (token, parent) { _step;
var lineNumber = token.lineNumber, try {
markup = token.markup; for (_iterator.s(); !(_step = _iterator.n()).done;) {
var markupStyle = emphasisOrStrongStyleFor(markup); var token = _step.value;
if (style === "consistent") { var children = token.children;
style = markupStyle; var childType = "".concat(type, "Sequence");
} var startSequence = tokenIfType(children[0], childType);
if (style !== markupStyle) { var endSequence = tokenIfType(children[children.length - 1], childType);
var rangeAndFixInfo = {}; if (startSequence && endSequence) {
var contentToken = getNextChildToken(parent, token, "text", "".concat(tagPrefix, "_close")); var markupStyle = emphasisOrStrongStyleFor(startSequence.text);
if (contentToken) { if (style === "consistent") {
var content = contentToken.content; style = markupStyle;
var actual = "".concat(markup).concat(content).concat(markup); }
var expectedMarkup = style === "asterisk" ? asterisk : underline; if (style !== markupStyle) {
var expected = "".concat(expectedMarkup).concat(content).concat(expectedMarkup); for (var _i = 0, _arr = [startSequence, endSequence]; _i < _arr.length; _i++) {
if (lastLineNumber !== lineNumber) { var sequence = _arr[_i];
lastLineNumber = lineNumber; addError(onError, sequence.startLine, "Expected: ".concat(style, "; Actual: ").concat(markupStyle), undefined, [sequence.startColumn, sequence.text.length], {
instances.clear(); "editColumn": sequence.startColumn,
"deleteCount": sequence.text.length,
"insertText": style === "asterisk" ? asterisk : underline
});
}
} }
var instance = (instances.get(expected) || 0) + 1;
instances.set(expected, instance);
rangeAndFixInfo = getRangeAndFixInfoIfFound(params.lines, lineNumber - 1, actual, expected, instance);
} }
addError(onError, lineNumber, "Expected: ".concat(style, "; Actual: ").concat(markupStyle), undefined, rangeAndFixInfo.range, rangeAndFixInfo.fixInfo);
} }
}); } catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}; };
module.exports = [{ module.exports = [{
"names": ["MD049", "emphasis-style"], "names": ["MD049", "emphasis-style"],
"description": "Emphasis style should be consistent", "description": "Emphasis style should be consistent",
"tags": ["emphasis"], "tags": ["emphasis"],
"function": function MD049(params, onError) { "function": function MD049(params, onError) {
return impl(params, onError, "em", "*", "_", params.config.style || undefined); return impl(params, onError, "emphasis", "*", "_", params.config.style || undefined);
} }
}, { }, {
"names": ["MD050", "strong-style"], "names": ["MD050", "strong-style"],

View file

@ -1080,66 +1080,6 @@ function applyFixes(input, errors) {
} }
module.exports.applyFixes = applyFixes; module.exports.applyFixes = applyFixes;
/**
* Gets the range and fixInfo values for reporting an error if the expected
* text is found on the specified line.
*
* @param {string[]} lines Lines of Markdown content.
* @param {number} lineIndex Line index to check.
* @param {string} search Text to search for.
* @param {string} replace Text to replace with.
* @param {number} [instance] Instance on the line (1-based).
* @returns {Object} Range and fixInfo wrapper.
*/
module.exports.getRangeAndFixInfoIfFound =
(lines, lineIndex, search, replace, instance = 1) => {
let range = null;
let fixInfo = null;
let searchIndex = -1;
while (instance > 0) {
searchIndex = lines[lineIndex].indexOf(search, searchIndex + 1);
instance--;
}
if (searchIndex !== -1) {
const column = searchIndex + 1;
const length = search.length;
range = [ column, length ];
fixInfo = {
"editColumn": column,
"deleteCount": length,
"insertText": replace
};
}
return {
range,
fixInfo
};
};
/**
* Gets the next (subsequent) child token if it is of the expected type.
*
* @param {Object} parentToken Parent token.
* @param {Object} childToken Child token basis.
* @param {string} nextType Token type of next token.
* @param {string} nextNextType Token type of next-next token.
* @returns {Object} Next token.
*/
function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
const { children } = parentToken;
const index = children.indexOf(childToken);
if (
(index !== -1) &&
(children.length > index + 2) &&
(children[index + 1].type === nextType) &&
(children[index + 2].type === nextNextType)
) {
return children[index + 1];
}
return null;
}
module.exports.getNextChildToken = getNextChildToken;
/** /**
* Expands a path with a tilde to an absolute path. * Expands a path with a tilde to an absolute path.
* *

View file

@ -203,6 +203,17 @@ function matchAndGetTokensByType(tokens, matchTypes, resultTypes) {
return result; return result;
} }
/**
* Returns the specified token iff it is of the desired type.
*
* @param {Token} token Micromark token candidate.
* @param {string} type Desired type.
* @returns {Token | null} Token instance.
*/
function tokenIfType(token, type) {
return (token && (token.type === type)) ? token : null;
}
module.exports = { module.exports = {
"parse": micromarkParse, "parse": micromarkParse,
filterByPredicate, filterByPredicate,
@ -210,5 +221,6 @@ module.exports = {
getHtmlTagInfo, getHtmlTagInfo,
getMicromarkEvents, getMicromarkEvents,
getTokenTextByType, getTokenTextByType,
matchAndGetTokensByType matchAndGetTokensByType,
tokenIfType
}; };

View file

@ -3,7 +3,7 @@
"use strict"; "use strict";
const { addErrorContext } = require("../helpers"); const { addErrorContext } = require("../helpers");
const { filterByTypes } = require("../helpers/micromark.cjs"); const { filterByTypes, tokenIfType } = require("../helpers/micromark.cjs");
const leftSpaceRe = /^\s(?:[^`]|$)/; const leftSpaceRe = /^\s(?:[^`]|$)/;
const rightSpaceRe = /[^`]\s$/; const rightSpaceRe = /[^`]\s$/;
@ -17,7 +17,6 @@ const trimCodeText = (text, start, end) => {
} }
return text; return text;
}; };
const tokenIfType = (token, type) => token && (token.type === type) && token;
module.exports = { module.exports = {
"names": [ "MD038", "no-space-in-code" ], "names": [ "MD038", "no-space-in-code" ],

View file

@ -2,54 +2,41 @@
"use strict"; "use strict";
const { addError, emphasisOrStrongStyleFor, forEachInlineChild, const { addError, emphasisOrStrongStyleFor } = require("../helpers");
getNextChildToken, getRangeAndFixInfoIfFound } = require("../helpers"); const { filterByTypes, tokenIfType } = require("../helpers/micromark.cjs");
const impl = const impl =
(params, onError, tagPrefix, asterisk, underline, style = "consistent") => { (params, onError, type, asterisk, underline, style = "consistent") => {
let lastLineNumber = -1; const emphasisTokens =
const instances = new Map(); filterByTypes(params.parsers.micromark.tokens, [ type ]);
forEachInlineChild(params, `${tagPrefix}_open`, (token, parent) => { for (const token of emphasisTokens) {
const { lineNumber, markup } = token; const { children } = token;
const markupStyle = emphasisOrStrongStyleFor(markup); const childType = `${type}Sequence`;
if (style === "consistent") { const startSequence = tokenIfType(children[0], childType);
style = markupStyle; const endSequence = tokenIfType(children[children.length - 1], childType);
} if (startSequence && endSequence) {
if (style !== markupStyle) { const markupStyle = emphasisOrStrongStyleFor(startSequence.text);
let rangeAndFixInfo = {}; if (style === "consistent") {
const contentToken = getNextChildToken( style = markupStyle;
parent, token, "text", `${tagPrefix}_close` }
); if (style !== markupStyle) {
if (contentToken) { for (const sequence of [ startSequence, endSequence ]) {
const { content } = contentToken; addError(
const actual = `${markup}${content}${markup}`; onError,
const expectedMarkup = sequence.startLine,
(style === "asterisk") ? asterisk : underline; `Expected: ${style}; Actual: ${markupStyle}`,
const expected = `${expectedMarkup}${content}${expectedMarkup}`; undefined,
if (lastLineNumber !== lineNumber) { [ sequence.startColumn, sequence.text.length ],
lastLineNumber = lineNumber; {
instances.clear(); "editColumn": sequence.startColumn,
} "deleteCount": sequence.text.length,
const instance = (instances.get(expected) || 0) + 1; "insertText": (style === "asterisk") ? asterisk : underline
instances.set(expected, instance); }
rangeAndFixInfo = getRangeAndFixInfoIfFound( );
params.lines, }
lineNumber - 1,
actual,
expected,
instance
);
} }
addError(
onError,
lineNumber,
`Expected: ${style}; Actual: ${markupStyle}`,
undefined,
rangeAndFixInfo.range,
rangeAndFixInfo.fixInfo
);
} }
}); }
}; };
module.exports = [ module.exports = [
@ -61,7 +48,7 @@ module.exports = [
return impl( return impl(
params, params,
onError, onError,
"em", "emphasis",
"*", "*",
"_", "_",
params.config.style || undefined params.config.style || undefined

View file

@ -34,12 +34,12 @@ Mixed __strong emphasis__ on **this** line __with__ multiple **issues** {MD050}
Inconsistent Inconsistent
emphasis _text {MD049} emphasis _text {MD049}
spanning_ many spanning_ many {MD049}
lines lines
Inconsistent Inconsistent
strong **emphasis {MD050} strong **emphasis {MD050}
spanning** many spanning** many {MD050}
lines lines
Inconsistent _double_ text _interleaved_ text _double_ _interleaved_ emphasis. {MD049} Inconsistent _double_ text _interleaved_ text _double_ _interleaved_ emphasis. {MD049}

File diff suppressed because it is too large Load diff

View file

@ -68,8 +68,8 @@ text text
* Item item item * Item item item
item * emphasis * item {MD037} item * emphasis * item {MD037}
Text _ emphasis {MD037} Text * emphasis {MD037}
emphasis _ text {MD037} emphasis * text {MD037}
Text ** bold {MD037} Text ** bold {MD037}
bold ** text {MD037} bold ** text {MD037}