From b6471fba315f0e39e0bc857ea5be23205aa8b714 Mon Sep 17 00:00:00 2001 From: David Anson Date: Wed, 8 Jun 2022 22:10:27 -0700 Subject: [PATCH] Enable ESLint rule unicorn/no-array-for-each, auto-fix all violations, manually address new issues for ~4% time reduction measured via profile-fixture.mjs on Apple Silicon M1. --- .eslintrc.json | 1 - demo/markdownlint-browser.js | 265 +++++++++++++------------ example/typescript/tsconfig.json | 2 +- helpers/helpers.js | 44 ++-- lib/markdownlint.js | 105 +++++----- lib/md004.js | 8 +- lib/md005.js | 8 +- lib/md006.js | 8 +- lib/md007.js | 8 +- lib/md013.js | 4 +- lib/md014.js | 8 +- lib/md027.js | 4 +- lib/md028.js | 4 +- lib/md029.js | 9 +- lib/md030.js | 8 +- lib/md032.js | 5 +- lib/md034.js | 4 +- lib/md036.js | 4 +- lib/md039.js | 4 +- lib/md042.js | 8 +- lib/md043.js | 4 +- lib/md046.js | 27 +-- lib/md048.js | 27 ++- lib/rules.js | 4 +- package.json | 2 +- schema/build-config-schema.js | 18 +- test/markdownlint-test-custom-rules.js | 60 +++--- test/markdownlint-test-extra-type.js | 8 +- test/markdownlint-test-helpers.js | 38 ++-- test/markdownlint-test-scenarios.js | 31 +-- test/markdownlint-test.js | 53 ++--- test/rules/letters-E-X.js | 12 +- test/rules/lint-javascript.js | 4 +- test/rules/npm/sample-rule.js | 4 +- 34 files changed, 414 insertions(+), 389 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 75dbebbb..b62e19c5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -196,7 +196,6 @@ "unicorn/consistent-function-scoping": "off", "unicorn/filename-case": "off", "unicorn/no-array-callback-reference": "off", - "unicorn/no-array-for-each": "off", "unicorn/no-keyword-prefix": "off", "unicorn/no-new-array": "off", "unicorn/no-null": "off", diff --git a/demo/markdownlint-browser.js b/demo/markdownlint-browser.js index 480507e7..fdedf37e 100644 --- a/demo/markdownlint-browser.js +++ b/demo/markdownlint-browser.js @@ -327,11 +327,11 @@ module.exports.getLineMetadata = function getLineMetadata(params) { filterTokens(params, "hr", (token) => { lineMetadata[token.map[0]][6] = true; }); - params.tokens.filter(isMathBlock).forEach((token) => { + for (const token of params.tokens.filter(isMathBlock)) { for (let i = token.map[0]; i < token.map[1]; i++) { lineMetadata[i][7] = true; } - }); + } return lineMetadata; }; /** @@ -343,9 +343,9 @@ module.exports.getLineMetadata = function getLineMetadata(params) { * @returns {void} */ function forEachLine(lineMetadata, handler) { - lineMetadata.forEach(function forMetadata(metadata) { + for (const metadata of lineMetadata) { handler(...metadata); - }); + } } module.exports.forEachLine = forEachLine; // Returns (nested) lists as a flat array (in order) @@ -356,7 +356,7 @@ module.exports.flattenLists = function flattenLists(tokens) { let nesting = 0; const nestingStack = []; let lastWithMap = { "map": [0, 1] }; - tokens.forEach((token) => { + for (const token of tokens) { if ((token.type === "bullet_list_open") || (token.type === "ordered_list_open")) { // Save current context and start a new one @@ -399,24 +399,24 @@ module.exports.flattenLists = function flattenLists(tokens) { // Track last token with map lastWithMap = token; } - }); + } return flattenedLists; }; // Calls the provided function for each specified inline child token module.exports.forEachInlineChild = function forEachInlineChild(params, type, handler) { filterTokens(params, "inline", function forToken(token) { - token.children.forEach(function forChild(child) { + for (const child of token.children) { if (child.type === type) { handler(child, token); } - }); + } }); }; // Calls the provided function for each heading's content module.exports.forEachHeading = function forEachHeading(params, handler) { let heading = null; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { if (token.type === "heading_open") { heading = token; } @@ -426,7 +426,7 @@ module.exports.forEachHeading = function forEachHeading(params, handler) { else if ((token.type === "inline") && heading) { handler(heading, token.content, token); } - }); + } }; /** * Calls the provided function for each inline code span's content. @@ -751,7 +751,7 @@ function emphasisMarkersInContent(params) { const { lines } = params; const byLine = new Array(lines.length); // Search links - lines.forEach((tokenLine, tokenLineIndex) => { + for (const [tokenLineIndex, tokenLine] of lines.entries()) { const inLine = []; forEachLink(tokenLine, (index, match) => { let markerMatch = null; @@ -760,7 +760,7 @@ function emphasisMarkersInContent(params) { } }); byLine[tokenLineIndex] = inLine; - }); + } // Search code spans filterTokens(params, "inline", (token) => { const { children, lineNumber, map } = token; @@ -768,7 +768,7 @@ function emphasisMarkersInContent(params) { const tokenLines = lines.slice(map[0], map[1]); forEachInlineCodeSpan(tokenLines.join("\n"), (code, lineIndex, column, tickCount) => { const codeLines = code.split(newLineRe); - codeLines.forEach((codeLine, codeLineIndex) => { + for (const [codeLineIndex, codeLine] of codeLines.entries()) { const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; const inLine = byLine[byLineIndex]; const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; @@ -777,7 +777,7 @@ function emphasisMarkersInContent(params) { inLine.push(codeLineOffset + match.index); } byLine[byLineIndex] = inLine; - }); + } }); } }); @@ -924,7 +924,7 @@ function getPreferredLineEnding(input, os) { let lf = 0; let crlf = 0; const endings = input.match(newLineRe) || []; - endings.forEach((ending) => { + for (const ending of endings) { // eslint-disable-next-line default-case switch (ending) { case "\r": @@ -937,7 +937,7 @@ function getPreferredLineEnding(input, os) { crlf++; break; } - }); + } let preferredLineEnding = null; if (!cr && !lf && !crlf) { preferredLineEnding = (os && os.EOL) || "\n"; @@ -1021,8 +1021,10 @@ function applyFixes(input, errors) { return unique; }); // Collapse insert/no-delete and no-insert/delete for same line/column - lastFixInfo = {}; - fixInfos.forEach((fixInfo) => { + lastFixInfo = { + "lineNumber": -1 + }; + for (const fixInfo of fixInfos) { if ((fixInfo.lineNumber === lastFixInfo.lineNumber) && (fixInfo.editColumn === lastFixInfo.editColumn) && !fixInfo.insertText && @@ -1033,12 +1035,12 @@ function applyFixes(input, errors) { lastFixInfo.lineNumber = 0; } lastFixInfo = fixInfo; - }); + } fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber); // Apply all (remaining/updated) fixes let lastLineIndex = -1; let lastEditIndex = -1; - fixInfos.forEach((fixInfo) => { + for (const fixInfo of fixInfos) { const { lineNumber, editColumn, deleteCount } = fixInfo; const lineIndex = lineNumber - 1; const editIndex = editColumn - 1; @@ -1050,7 +1052,7 @@ function applyFixes(input, errors) { } lastLineIndex = lineIndex; lastEditIndex = editIndex; - }); + } // Return corrected input return lines.filter((line) => line !== null).join(lineEnding); } @@ -1289,31 +1291,31 @@ function validateRuleList(ruleList, synchronous) { return result; } const allIds = {}; - ruleList.forEach(function forRule(rule, index) { + for (const [index, rule] of ruleList.entries()) { const customIndex = index - rules.length; - // eslint-disable-next-line jsdoc/require-jsdoc + // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc function newError(property) { return new Error("Property '" + property + "' of custom rule at index " + customIndex + " is incorrect."); } - ["names", "tags"].forEach(function forProperty(property) { + for (const property of ["names", "tags"]) { const value = rule[property]; if (!result && (!value || !Array.isArray(value) || (value.length === 0) || !value.every(helpers.isString) || value.some(helpers.isEmptyString))) { result = newError(property); } - }); - [ + } + for (const propertyInfo of [ ["description", "string"], ["function", "function"] - ].forEach(function forProperty(propertyInfo) { + ]) { const property = propertyInfo[0]; const value = rule[property]; if (!result && (!value || (typeof value !== propertyInfo[1]))) { result = newError(property); } - }); + } if (!result && rule.information && (Object.getPrototypeOf(rule.information) !== URL.prototype)) { @@ -1329,24 +1331,25 @@ function validateRuleList(ruleList, synchronous) { " is asynchronous and can not be used in a synchronous context."); } if (!result) { - rule.names.forEach(function forName(name) { + for (const name of rule.names) { const nameUpper = name.toUpperCase(); if (!result && (allIds[nameUpper] !== undefined)) { result = new Error("Name '" + name + "' of custom rule at index " + customIndex + " is already used as a name or tag."); } allIds[nameUpper] = true; - }); - rule.tags.forEach(function forTag(tag) { + } + for (const tag of rule.tags) { const tagUpper = tag.toUpperCase(); if (!result && allIds[tagUpper]) { result = new Error("Tag '" + tag + "' of custom rule at index " + customIndex + " is already used as a name."); } allIds[tagUpper] = false; - }); + } } - }); + } + // @ts-ignore return result; } /** @@ -1363,10 +1366,10 @@ function newResults(ruleList) { const results = []; const keys = Object.keys(lintResults); keys.sort(); - keys.forEach(function forFile(file) { + for (const file of keys) { const fileResults = lintResults[file]; if (Array.isArray(fileResults)) { - fileResults.forEach(function forResult(result) { + for (const result of fileResults) { const ruleMoniker = result.ruleNames ? result.ruleNames.join("/") : (result.ruleName + "/" + result.ruleAlias); @@ -1380,30 +1383,32 @@ function newResults(ruleList) { (result.errorContext ? " [Context: \"" + result.errorContext + "\"]" : "")); - }); + } } else { if (!ruleNameToRule) { ruleNameToRule = {}; - ruleList.forEach(function forRule(rule) { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); ruleNameToRule[ruleName] = rule; - }); + } } - Object.keys(fileResults).forEach(function forRule(ruleName) { + for (const [ruleName, ruleResults] of Object.entries(fileResults)) { const rule = ruleNameToRule[ruleName.toUpperCase()]; - const ruleResults = fileResults[ruleName]; - ruleResults.forEach(function forLine(lineNumber) { + for (const lineNumber of ruleResults) { + // @ts-ignore const nameIndex = Math.min(useAlias ? 1 : 0, rule.names.length - 1); const result = file + ": " + lineNumber + ": " + + // @ts-ignore rule.names[nameIndex] + " " + + // @ts-ignore rule.description; results.push(result); - }); - }); + } + } } - }); + } return results.join("\n"); } Object.defineProperty(lintResults, "toString", { "value": toString }); @@ -1445,7 +1450,7 @@ function removeFrontMatter(content, frontMatter) { */ function annotateTokens(tokens, lines) { let trMap = null; - tokens.forEach(function forToken(token) { + for (const token of tokens) { // Provide missing maps for table content if (token.type === "tr_open") { trMap = token.map; @@ -1475,7 +1480,7 @@ function annotateTokens(tokens, lines) { helpers.forEachInlineCodeSpan(token.content, function handleInlineCodeSpan(code) { codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); }); - (token.children || []).forEach(function forChild(child) { + for (const child of (token.children || [])) { child.lineNumber = lineNumber; child.line = lines[lineNumber - 1]; if ((child.type === "softbreak") || (child.type === "hardbreak")) { @@ -1484,9 +1489,9 @@ function annotateTokens(tokens, lines) { else if (child.type === "code_inline") { lineNumber += codeSpanExtraLines.shift(); } - }); + } } - }); + } } /** * Map rule names/tags to canonical rule name. @@ -1497,24 +1502,24 @@ function annotateTokens(tokens, lines) { function mapAliasToRuleNames(ruleList) { const aliasToRuleNames = {}; // const tagToRuleNames = {}; - ruleList.forEach(function forRule(rule) { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); // The following is useful for updating README.md: // console.log( // "* **[" + ruleName + "](doc/Rules.md#" + ruleName.toLowerCase() + // ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description); - rule.names.forEach(function forName(name) { + for (const name of rule.names) { const nameUpper = name.toUpperCase(); aliasToRuleNames[nameUpper] = [ruleName]; - }); - rule.tags.forEach(function forTag(tag) { + } + for (const tag of rule.tags) { const tagUpper = tag.toUpperCase(); const ruleNames = aliasToRuleNames[tagUpper] || []; ruleNames.push(ruleName); aliasToRuleNames[tagUpper] = ruleNames; // tagToRuleNames[tag] = ruleName; - }); - }); + } + } // The following is useful for updating README.md: // Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) { // console.log("* **" + tag + "** - " + @@ -1536,14 +1541,14 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) { const defaultKey = Object.keys(config).filter((key) => key.toUpperCase() === "DEFAULT"); const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]]; const effectiveConfig = {}; - ruleList.forEach((rule) => { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); effectiveConfig[ruleName] = ruleDefault; - }); - deprecatedRuleNames.forEach((ruleName) => { + } + for (const ruleName of deprecatedRuleNames) { effectiveConfig[ruleName] = false; - }); - Object.keys(config).forEach((key) => { + } + for (const key of Object.keys(config)) { let value = config[key]; if (value) { if (!(value instanceof Object)) { @@ -1554,10 +1559,10 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) { value = false; } const keyUpper = key.toUpperCase(); - (aliasToRuleNames[keyUpper] || []).forEach((ruleName) => { + for (const ruleName of (aliasToRuleNames[keyUpper] || [])) { effectiveConfig[ruleName] = value; - }); - }); + } + } return effectiveConfig; } /** @@ -1615,7 +1620,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin // Helper functions // eslint-disable-next-line jsdoc/require-jsdoc function handleInlineConfig(input, forEachMatch, forEachLine) { - input.forEach((line, lineIndex) => { + for (const [lineIndex, line] of input.entries()) { if (!noInlineConfig) { let match = null; while ((match = helpers.inlineCommentStartRe.exec(line))) { @@ -1632,7 +1637,7 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin if (forEachLine) { forEachLine(); } - }); + } } // eslint-disable-next-line jsdoc/require-jsdoc function configureFile(action, parameter) { @@ -1649,11 +1654,11 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin const enabled = (action.startsWith("ENABLE")); const trimmed = parameter && parameter.trim(); const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames; - items.forEach((nameUpper) => { - (aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { + for (const nameUpper of items) { + for (const ruleName of (aliasToRuleNames[nameUpper] || [])) { state[ruleName] = enabled; - }); - }); + } + } return state; } // eslint-disable-next-line jsdoc/require-jsdoc @@ -1691,11 +1696,11 @@ function getEnabledRulesPerLineNumber(ruleList, lines, frontMatterLines, noInlin // Handle inline comments handleInlineConfig([lines.join("\n")], configureFile); const effectiveConfig = getEffectiveConfig(ruleList, config, aliasToRuleNames); - ruleList.forEach((rule) => { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); allRuleNames.push(ruleName); enabledRules[ruleName] = !!effectiveConfig[ruleName]; - }); + } capturedRules = enabledRules; handleInlineConfig(lines, enableDisableFile); handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState); @@ -2019,10 +2024,10 @@ function lintInput(options, synchronous, callback) { 3 : options.resultVersion; const md = markdownIt({ "html": true }); const markdownItPlugins = options.markdownItPlugins || []; - markdownItPlugins.forEach(function forPlugin(plugin) { + for (const plugin of markdownItPlugins) { // @ts-ignore md.use(...plugin); - }); + } const fs = options.fs || __webpack_require__(/*! fs */ "?ec0a"); const results = newResults(ruleList); let done = false; @@ -2429,12 +2434,12 @@ module.exports = { const style = String(params.config.style || "consistent"); let expectedStyle = style; const nestingStyles = []; - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered) { if (expectedStyle === "consistent") { expectedStyle = unorderedListStyleFor(list.items[0]); } - list.items.forEach((item) => { + for (const item of list.items) { const itemStyle = unorderedListStyleFor(item); if (style === "sublist") { const nesting = list.nesting; @@ -2463,9 +2468,9 @@ module.exports = { }; } addErrorDetailIf(onError, item.lineNumber, expectedStyle, itemStyle, null, null, range, fixInfo); - }); + } } - }); + } } }; @@ -2488,12 +2493,12 @@ module.exports = { "description": "Inconsistent indentation for list items at the same level", "tags": ["bullet", "ul", "indentation"], "function": function MD005(params, onError) { - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { const expectedIndent = list.indent; let expectedEnd = 0; let actualEnd = -1; let endMatching = false; - list.items.forEach((item) => { + for (const item of list.items) { const { line, lineNumber } = item; const actualIndent = indentFor(item); let match = null; @@ -2528,8 +2533,8 @@ module.exports = { } } } - }); - }); + } + } } }; @@ -2552,16 +2557,16 @@ module.exports = { "description": "Consider starting bulleted lists at the beginning of the line", "tags": ["bullet", "ul", "indentation"], "function": function MD006(params, onError) { - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered && !list.nesting && (list.indent !== 0)) { - list.items.forEach((item) => { + for (const item of list.items) { const { lineNumber, line } = item; addErrorDetailIf(onError, lineNumber, 0, list.indent, null, null, rangeFromRegExp(line, listItemMarkerRe), { "deleteCount": line.length - line.trimStart().length }); - }); + } } - }); + } } }; @@ -2587,9 +2592,9 @@ module.exports = { const indent = Number(params.config.indent || 2); const startIndented = !!params.config.start_indented; const startIndent = Number(params.config.start_indent || indent); - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered && list.parentsUnordered) { - list.items.forEach((item) => { + for (const item of list.items) { const { lineNumber, line } = item; const expectedIndent = (startIndented ? startIndent : 0) + (list.nesting * indent); @@ -2606,9 +2611,9 @@ module.exports = { "deleteCount": actualIndent, "insertText": "".padEnd(expectedIndent) }); - }); + } } - }); + } } }; @@ -2879,11 +2884,11 @@ module.exports = { const linkOnlyLineNumbers = []; filterTokens(params, "inline", (token) => { let childTokenTypes = ""; - token.children.forEach((child) => { + for (const child of token.children) { if (child.type !== "text" || child.content !== "") { childTokenTypes += tokenTypeMap[child.type] || "x"; } - }); + } if (linkOrImageOnlyLineRe.test(childTokenTypes)) { linkOnlyLineNumbers.push(token.lineNumber); } @@ -2930,7 +2935,7 @@ module.exports = { "description": "Dollar signs used before commands without showing output", "tags": ["code"], "function": function MD014(params, onError) { - ["code_block", "fence"].forEach((type) => { + for (const type of ["code_block", "fence"]) { filterTokens(params, type, (token) => { const margin = (token.type === "fence") ? 1 : 0; const dollarInstances = []; @@ -2951,16 +2956,16 @@ module.exports = { } } if (allDollars) { - dollarInstances.forEach((instance) => { + for (const instance of dollarInstances) { const [i, lineTrim, column, length] = instance; addErrorContext(onError, i + 1, lineTrim, null, null, [column, length], { "editColumn": column, "deleteCount": length }); - }); + } } }); - }); + } } }; @@ -3362,7 +3367,7 @@ module.exports = { "function": function MD027(params, onError) { let blockquoteNesting = 0; let listItemNesting = 0; - params.tokens.forEach((token) => { + for (const token of params.tokens) { const { content, lineNumber, type } = token; if (type === "blockquote_open") { blockquoteNesting++; @@ -3392,7 +3397,7 @@ module.exports = { } } } - }); + } } }; @@ -3416,7 +3421,7 @@ module.exports = { "function": function MD028(params, onError) { let prevToken = {}; let prevLineNumber = null; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { if ((token.type === "blockquote_open") && (prevToken.type === "blockquote_close")) { for (let lineNumber = prevLineNumber; lineNumber < token.lineNumber; lineNumber++) { @@ -3427,7 +3432,7 @@ module.exports = { if (token.type === "blockquote_open") { prevLineNumber = token.map[1] + 1; } - }); + } } }; @@ -3456,7 +3461,8 @@ module.exports = { "tags": ["ol"], "function": function MD029(params, onError) { const style = String(params.config.style || "one_or_ordered"); - flattenedLists().filter((list) => !list.unordered).forEach((list) => { + const filteredLists = flattenedLists().filter((list) => !list.unordered); + for (const list of filteredLists) { const { items } = list; let current = 1; let incrementing = false; @@ -3488,7 +3494,7 @@ module.exports = { current = 1; } // Validate each list item marker - items.forEach((item) => { + for (const item of items) { const match = orderedListItemMarkerRe.exec(item.line); if (match) { addErrorDetailIf(onError, item.lineNumber, String(current), match[1], "Style: " + listStyleExamples[listStyle], null, rangeFromRegExp(item.line, listItemMarkerRe)); @@ -3496,8 +3502,8 @@ module.exports = { current++; } } - }); - }); + } + } } }; @@ -3524,13 +3530,13 @@ module.exports = { const olSingle = Number(params.config.ol_single || 1); const ulMulti = Number(params.config.ul_multi || 1); const olMulti = Number(params.config.ol_multi || 1); - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { const lineCount = list.lastLineIndex - list.open.map[0]; const allSingle = lineCount === list.items.length; const expectedSpaces = list.unordered ? (allSingle ? ulSingle : ulMulti) : (allSingle ? olSingle : olMulti); - list.items.forEach((item) => { + for (const item of list.items) { const { line, lineNumber } = item; const match = /^[\s>]*\S+(\s*)/.exec(line); const [{ "length": matchLength }, { "length": actualSpaces }] = match; @@ -3545,8 +3551,8 @@ module.exports = { } addErrorDetailIf(onError, lineNumber, expectedSpaces, actualSpaces, null, null, [1, matchLength], fixInfo); } - }); - }); + } + } } }; @@ -3611,7 +3617,8 @@ module.exports = { "tags": ["bullet", "ul", "ol", "blank_lines"], "function": function MD032(params, onError) { const { lines } = params; - flattenedLists().filter((list) => !list.nesting).forEach((list) => { + const filteredLists = flattenedLists().filter((list) => !list.nesting); + for (const list of filteredLists) { const firstIndex = list.open.map[0]; if (!isBlankLine(lines[firstIndex - 1])) { const line = lines[firstIndex]; @@ -3629,7 +3636,7 @@ module.exports = { "insertText": `${quotePrefix}\n` }); } - }); + } } }; @@ -3703,7 +3710,7 @@ module.exports = { "function": function MD034(params, onError) { filterTokens(params, "inline", (token) => { let inLink = false; - token.children.forEach((child) => { + for (const child of token.children) { const { content, line, lineNumber, type } = child; let match = null; if (type === "link_open") { @@ -3739,7 +3746,7 @@ module.exports = { } } } - }); + } }); } }; @@ -3837,9 +3844,9 @@ module.exports = { return base; } let state = base; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { state = state(token); - }); + } } }; @@ -4101,7 +4108,7 @@ module.exports = { let inLink = false; let linkText = ""; let lineIndex = 0; - children.forEach((child) => { + for (const child of children) { const { content, markup, type } = child; if (type === "link_open") { inLink = true; @@ -4139,7 +4146,7 @@ module.exports = { `${markup}${content}${markup}` : (content || markup); } - }); + } }); } }; @@ -4240,15 +4247,15 @@ module.exports = { let inLink = false; let linkText = ""; let emptyLink = false; - token.children.forEach(function forChild(child) { + for (const child of token.children) { if (child.type === "link_open") { inLink = true; linkText = ""; - child.attrs.forEach(function forAttr(attr) { + for (const attr of child.attrs) { if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) { emptyLink = true; } - }); + } } else if (child.type === "link_close") { inLink = false; @@ -4267,7 +4274,7 @@ module.exports = { else if (inLink) { linkText += child.content; } - }); + } }); } }; @@ -4293,9 +4300,9 @@ module.exports = { const requiredHeadings = params.config.headings || params.config.headers; if (Array.isArray(requiredHeadings)) { const levels = {}; - [1, 2, 3, 4, 5, 6].forEach((level) => { + for (const level of [1, 2, 3, 4, 5, 6]) { levels["h" + level] = "######".substr(-level); - }); + } let i = 0; let matchAny = false; let hasError = false; @@ -4466,15 +4473,14 @@ module.exports = { "tags": ["code"], "function": function MD046(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - params.tokens - .filter((token) => token.type === "code_block" || token.type === "fence") - .forEach((token) => { + const codeBlocksAndFences = params.tokens.filter((token) => (token.type === "code_block") || (token.type === "fence")); + for (const token of codeBlocksAndFences) { const { lineNumber, type } = token; if (expectedStyle === "consistent") { expectedStyle = tokenTypeToStyle[type]; } addErrorDetailIf(onError, lineNumber, expectedStyle, tokenTypeToStyle[type]); - }); + } } }; @@ -4527,15 +4533,14 @@ module.exports = { "function": function MD048(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; - params.tokens - .filter((token) => token.type === "fence") - .forEach((fenceToken) => { + const fenceTokens = params.tokens.filter((token) => token.type === "fence"); + for (const fenceToken of fenceTokens) { const { lineNumber, markup } = fenceToken; if (expectedStyle === "consistent") { expectedStyle = fencedCodeBlockStyleFor(markup); } addErrorDetailIf(onError, lineNumber, expectedStyle, fencedCodeBlockStyleFor(markup)); - }); + } } }; @@ -4825,12 +4830,12 @@ const rules = [ __webpack_require__(/*! ./md052 */ "../lib/md052.js"), __webpack_require__(/*! ./md053 */ "../lib/md053.js") ]; -rules.forEach((rule) => { +for (const rule of rules) { const name = rule.names[0].toLowerCase(); // eslint-disable-next-line dot-notation rule["information"] = new URL(`${homepage}/blob/v${version}/doc/Rules.md#${name}`); -}); +} module.exports = rules; diff --git a/example/typescript/tsconfig.json b/example/typescript/tsconfig.json index 83a96607..5e9e1975 100644 --- a/example/typescript/tsconfig.json +++ b/example/typescript/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "strict": true, /* Enable all strict type-checking options. */ "noUnusedLocals": true, /* Report errors on unused locals. */ diff --git a/helpers/helpers.js b/helpers/helpers.js index a5540790..2c2709b4 100644 --- a/helpers/helpers.js +++ b/helpers/helpers.js @@ -328,11 +328,11 @@ module.exports.getLineMetadata = function getLineMetadata(params) { filterTokens(params, "hr", (token) => { lineMetadata[token.map[0]][6] = true; }); - params.tokens.filter(isMathBlock).forEach((token) => { + for (const token of params.tokens.filter(isMathBlock)) { for (let i = token.map[0]; i < token.map[1]; i++) { lineMetadata[i][7] = true; } - }); + } return lineMetadata; }; @@ -345,9 +345,9 @@ module.exports.getLineMetadata = function getLineMetadata(params) { * @returns {void} */ function forEachLine(lineMetadata, handler) { - lineMetadata.forEach(function forMetadata(metadata) { + for (const metadata of lineMetadata) { handler(...metadata); - }); + } } module.exports.forEachLine = forEachLine; @@ -359,7 +359,7 @@ module.exports.flattenLists = function flattenLists(tokens) { let nesting = 0; const nestingStack = []; let lastWithMap = { "map": [ 0, 1 ] }; - tokens.forEach((token) => { + for (const token of tokens) { if ((token.type === "bullet_list_open") || (token.type === "ordered_list_open")) { // Save current context and start a new one @@ -398,7 +398,7 @@ module.exports.flattenLists = function flattenLists(tokens) { // Track last token with map lastWithMap = token; } - }); + } return flattenedLists; }; @@ -406,18 +406,18 @@ module.exports.flattenLists = function flattenLists(tokens) { module.exports.forEachInlineChild = function forEachInlineChild(params, type, handler) { filterTokens(params, "inline", function forToken(token) { - token.children.forEach(function forChild(child) { + for (const child of token.children) { if (child.type === type) { handler(child, token); } - }); + } }); }; // Calls the provided function for each heading's content module.exports.forEachHeading = function forEachHeading(params, handler) { let heading = null; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { if (token.type === "heading_open") { heading = token; } else if (token.type === "heading_close") { @@ -425,7 +425,7 @@ module.exports.forEachHeading = function forEachHeading(params, handler) { } else if ((token.type === "inline") && heading) { handler(heading, token.content, token); } - }); + } }; /** @@ -767,7 +767,7 @@ function emphasisMarkersInContent(params) { const { lines } = params; const byLine = new Array(lines.length); // Search links - lines.forEach((tokenLine, tokenLineIndex) => { + for (const [ tokenLineIndex, tokenLine ] of lines.entries()) { const inLine = []; forEachLink(tokenLine, (index, match) => { let markerMatch = null; @@ -776,7 +776,7 @@ function emphasisMarkersInContent(params) { } }); byLine[tokenLineIndex] = inLine; - }); + } // Search code spans filterTokens(params, "inline", (token) => { const { children, lineNumber, map } = token; @@ -786,7 +786,7 @@ function emphasisMarkersInContent(params) { tokenLines.join("\n"), (code, lineIndex, column, tickCount) => { const codeLines = code.split(newLineRe); - codeLines.forEach((codeLine, codeLineIndex) => { + for (const [ codeLineIndex, codeLine ] of codeLines.entries()) { const byLineIndex = lineNumber - 1 + lineIndex + codeLineIndex; const inLine = byLine[byLineIndex]; const codeLineOffset = codeLineIndex ? 0 : column - 1 + tickCount; @@ -795,7 +795,7 @@ function emphasisMarkersInContent(params) { inLine.push(codeLineOffset + match.index); } byLine[byLineIndex] = inLine; - }); + } } ); } @@ -952,7 +952,7 @@ function getPreferredLineEnding(input, os) { let lf = 0; let crlf = 0; const endings = input.match(newLineRe) || []; - endings.forEach((ending) => { + for (const ending of endings) { // eslint-disable-next-line default-case switch (ending) { case "\r": @@ -965,7 +965,7 @@ function getPreferredLineEnding(input, os) { crlf++; break; } - }); + } let preferredLineEnding = null; if (!cr && !lf && !crlf) { preferredLineEnding = (os && os.EOL) || "\n"; @@ -1053,8 +1053,10 @@ function applyFixes(input, errors) { return unique; }); // Collapse insert/no-delete and no-insert/delete for same line/column - lastFixInfo = {}; - fixInfos.forEach((fixInfo) => { + lastFixInfo = { + "lineNumber": -1 + }; + for (const fixInfo of fixInfos) { if ( (fixInfo.lineNumber === lastFixInfo.lineNumber) && (fixInfo.editColumn === lastFixInfo.editColumn) && @@ -1066,12 +1068,12 @@ function applyFixes(input, errors) { lastFixInfo.lineNumber = 0; } lastFixInfo = fixInfo; - }); + } fixInfos = fixInfos.filter((fixInfo) => fixInfo.lineNumber); // Apply all (remaining/updated) fixes let lastLineIndex = -1; let lastEditIndex = -1; - fixInfos.forEach((fixInfo) => { + for (const fixInfo of fixInfos) { const { lineNumber, editColumn, deleteCount } = fixInfo; const lineIndex = lineNumber - 1; const editIndex = editColumn - 1; @@ -1085,7 +1087,7 @@ function applyFixes(input, errors) { } lastLineIndex = lineIndex; lastEditIndex = editIndex; - }); + } // Return corrected input return lines.filter((line) => line !== null).join(lineEnding); } diff --git a/lib/markdownlint.js b/lib/markdownlint.js index 0b4a82ea..4caf8cb1 100644 --- a/lib/markdownlint.js +++ b/lib/markdownlint.js @@ -29,32 +29,32 @@ function validateRuleList(ruleList, synchronous) { return result; } const allIds = {}; - ruleList.forEach(function forRule(rule, index) { + for (const [ index, rule ] of ruleList.entries()) { const customIndex = index - rules.length; - // eslint-disable-next-line jsdoc/require-jsdoc + // eslint-disable-next-line no-inner-declarations, jsdoc/require-jsdoc function newError(property) { return new Error( "Property '" + property + "' of custom rule at index " + customIndex + " is incorrect."); } - [ "names", "tags" ].forEach(function forProperty(property) { + for (const property of [ "names", "tags" ]) { const value = rule[property]; if (!result && (!value || !Array.isArray(value) || (value.length === 0) || !value.every(helpers.isString) || value.some(helpers.isEmptyString))) { result = newError(property); } - }); - [ + } + for (const propertyInfo of [ [ "description", "string" ], [ "function", "function" ] - ].forEach(function forProperty(propertyInfo) { + ]) { const property = propertyInfo[0]; const value = rule[property]; if (!result && (!value || (typeof value !== propertyInfo[1]))) { result = newError(property); } - }); + } if ( !result && rule.information && @@ -76,24 +76,25 @@ function validateRuleList(ruleList, synchronous) { ); } if (!result) { - rule.names.forEach(function forName(name) { + for (const name of rule.names) { const nameUpper = name.toUpperCase(); if (!result && (allIds[nameUpper] !== undefined)) { result = new Error("Name '" + name + "' of custom rule at index " + customIndex + " is already used as a name or tag."); } allIds[nameUpper] = true; - }); - rule.tags.forEach(function forTag(tag) { + } + for (const tag of rule.tags) { const tagUpper = tag.toUpperCase(); if (!result && allIds[tagUpper]) { result = new Error("Tag '" + tag + "' of custom rule at index " + customIndex + " is already used as a name."); } allIds[tagUpper] = false; - }); + } } - }); + } + // @ts-ignore return result; } @@ -111,10 +112,10 @@ function newResults(ruleList) { const results = []; const keys = Object.keys(lintResults); keys.sort(); - keys.forEach(function forFile(file) { + for (const file of keys) { const fileResults = lintResults[file]; if (Array.isArray(fileResults)) { - fileResults.forEach(function forResult(result) { + for (const result of fileResults) { const ruleMoniker = result.ruleNames ? result.ruleNames.join("/") : (result.ruleName + "/" + result.ruleAlias); @@ -129,30 +130,32 @@ function newResults(ruleList) { (result.errorContext ? " [Context: \"" + result.errorContext + "\"]" : "")); - }); + } } else { if (!ruleNameToRule) { ruleNameToRule = {}; - ruleList.forEach(function forRule(rule) { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); ruleNameToRule[ruleName] = rule; - }); + } } - Object.keys(fileResults).forEach(function forRule(ruleName) { + for (const [ ruleName, ruleResults ] of Object.entries(fileResults)) { const rule = ruleNameToRule[ruleName.toUpperCase()]; - const ruleResults = fileResults[ruleName]; - ruleResults.forEach(function forLine(lineNumber) { + for (const lineNumber of ruleResults) { + // @ts-ignore const nameIndex = Math.min(useAlias ? 1 : 0, rule.names.length - 1); const result = file + ": " + lineNumber + ": " + + // @ts-ignore rule.names[nameIndex] + " " + + // @ts-ignore rule.description; results.push(result); - }); - }); + } + } } - }); + } return results.join("\n"); } Object.defineProperty(lintResults, "toString", { "value": toString }); @@ -196,7 +199,7 @@ function removeFrontMatter(content, frontMatter) { */ function annotateTokens(tokens, lines) { let trMap = null; - tokens.forEach(function forToken(token) { + for (const token of tokens) { // Provide missing maps for table content if (token.type === "tr_open") { trMap = token.map; @@ -228,7 +231,7 @@ function annotateTokens(tokens, lines) { codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); } ); - (token.children || []).forEach(function forChild(child) { + for (const child of (token.children || [])) { child.lineNumber = lineNumber; child.line = lines[lineNumber - 1]; if ((child.type === "softbreak") || (child.type === "hardbreak")) { @@ -236,9 +239,9 @@ function annotateTokens(tokens, lines) { } else if (child.type === "code_inline") { lineNumber += codeSpanExtraLines.shift(); } - }); + } } - }); + } } /** @@ -250,24 +253,24 @@ function annotateTokens(tokens, lines) { function mapAliasToRuleNames(ruleList) { const aliasToRuleNames = {}; // const tagToRuleNames = {}; - ruleList.forEach(function forRule(rule) { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); // The following is useful for updating README.md: // console.log( // "* **[" + ruleName + "](doc/Rules.md#" + ruleName.toLowerCase() + // ")** *" + rule.names.slice(1).join("/") + "* - " + rule.description); - rule.names.forEach(function forName(name) { + for (const name of rule.names) { const nameUpper = name.toUpperCase(); aliasToRuleNames[nameUpper] = [ ruleName ]; - }); - rule.tags.forEach(function forTag(tag) { + } + for (const tag of rule.tags) { const tagUpper = tag.toUpperCase(); const ruleNames = aliasToRuleNames[tagUpper] || []; ruleNames.push(ruleName); aliasToRuleNames[tagUpper] = ruleNames; // tagToRuleNames[tag] = ruleName; - }); - }); + } + } // The following is useful for updating README.md: // Object.keys(tagToRuleNames).sort().forEach(function forTag(tag) { // console.log("* **" + tag + "** - " + @@ -292,14 +295,14 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) { ); const ruleDefault = (defaultKey.length === 0) || !!config[defaultKey[0]]; const effectiveConfig = {}; - ruleList.forEach((rule) => { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); effectiveConfig[ruleName] = ruleDefault; - }); - deprecatedRuleNames.forEach((ruleName) => { + } + for (const ruleName of deprecatedRuleNames) { effectiveConfig[ruleName] = false; - }); - Object.keys(config).forEach((key) => { + } + for (const key of Object.keys(config)) { let value = config[key]; if (value) { if (!(value instanceof Object)) { @@ -309,10 +312,10 @@ function getEffectiveConfig(ruleList, config, aliasToRuleNames) { value = false; } const keyUpper = key.toUpperCase(); - (aliasToRuleNames[keyUpper] || []).forEach((ruleName) => { + for (const ruleName of (aliasToRuleNames[keyUpper] || [])) { effectiveConfig[ruleName] = value; - }); - }); + } + } return effectiveConfig; } @@ -378,7 +381,7 @@ function getEnabledRulesPerLineNumber( // Helper functions // eslint-disable-next-line jsdoc/require-jsdoc function handleInlineConfig(input, forEachMatch, forEachLine) { - input.forEach((line, lineIndex) => { + for (const [ lineIndex, line ] of input.entries()) { if (!noInlineConfig) { let match = null; while ((match = helpers.inlineCommentStartRe.exec(line))) { @@ -395,7 +398,7 @@ function getEnabledRulesPerLineNumber( if (forEachLine) { forEachLine(); } - }); + } } // eslint-disable-next-line jsdoc/require-jsdoc function configureFile(action, parameter) { @@ -417,11 +420,11 @@ function getEnabledRulesPerLineNumber( const enabled = (action.startsWith("ENABLE")); const trimmed = parameter && parameter.trim(); const items = trimmed ? trimmed.toUpperCase().split(/\s+/) : allRuleNames; - items.forEach((nameUpper) => { - (aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { + for (const nameUpper of items) { + for (const ruleName of (aliasToRuleNames[nameUpper] || [])) { state[ruleName] = enabled; - }); - }); + } + } return state; } // eslint-disable-next-line jsdoc/require-jsdoc @@ -463,11 +466,11 @@ function getEnabledRulesPerLineNumber( handleInlineConfig([ lines.join("\n") ], configureFile); const effectiveConfig = getEffectiveConfig( ruleList, config, aliasToRuleNames); - ruleList.forEach((rule) => { + for (const rule of ruleList) { const ruleName = rule.names[0].toUpperCase(); allRuleNames.push(ruleName); enabledRules[ruleName] = !!effectiveConfig[ruleName]; - }); + } capturedRules = enabledRules; handleInlineConfig(lines, enableDisableFile); handleInlineConfig(lines, captureRestoreEnableDisable, updateLineState); @@ -844,10 +847,10 @@ function lintInput(options, synchronous, callback) { 3 : options.resultVersion; const md = markdownIt({ "html": true }); const markdownItPlugins = options.markdownItPlugins || []; - markdownItPlugins.forEach(function forPlugin(plugin) { + for (const plugin of markdownItPlugins) { // @ts-ignore md.use(...plugin); - }); + } const fs = options.fs || require("fs"); const results = newResults(ruleList); let done = false; diff --git a/lib/md004.js b/lib/md004.js index a79106fc..5cfde849 100644 --- a/lib/md004.js +++ b/lib/md004.js @@ -26,12 +26,12 @@ module.exports = { const style = String(params.config.style || "consistent"); let expectedStyle = style; const nestingStyles = []; - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered) { if (expectedStyle === "consistent") { expectedStyle = unorderedListStyleFor(list.items[0]); } - list.items.forEach((item) => { + for (const item of list.items) { const itemStyle = unorderedListStyleFor(item); if (style === "sublist") { const nesting = list.nesting; @@ -69,8 +69,8 @@ module.exports = { range, fixInfo ); - }); + } } - }); + } } }; diff --git a/lib/md005.js b/lib/md005.js index 9ac565de..696fce1d 100644 --- a/lib/md005.js +++ b/lib/md005.js @@ -11,12 +11,12 @@ module.exports = { "description": "Inconsistent indentation for list items at the same level", "tags": [ "bullet", "ul", "indentation" ], "function": function MD005(params, onError) { - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { const expectedIndent = list.indent; let expectedEnd = 0; let actualEnd = -1; let endMatching = false; - list.items.forEach((item) => { + for (const item of list.items) { const { line, lineNumber } = item; const actualIndent = indentFor(item); let match = null; @@ -63,7 +63,7 @@ module.exports = { } } } - }); - }); + } + } } }; diff --git a/lib/md006.js b/lib/md006.js index 93a746df..8bff486a 100644 --- a/lib/md006.js +++ b/lib/md006.js @@ -12,9 +12,9 @@ module.exports = { "Consider starting bulleted lists at the beginning of the line", "tags": [ "bullet", "ul", "indentation" ], "function": function MD006(params, onError) { - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered && !list.nesting && (list.indent !== 0)) { - list.items.forEach((item) => { + for (const item of list.items) { const { lineNumber, line } = item; addErrorDetailIf( onError, @@ -27,8 +27,8 @@ module.exports = { { "deleteCount": line.length - line.trimStart().length }); - }); + } } - }); + } } }; diff --git a/lib/md007.js b/lib/md007.js index 1bb6f342..58cd8b62 100644 --- a/lib/md007.js +++ b/lib/md007.js @@ -14,9 +14,9 @@ module.exports = { const indent = Number(params.config.indent || 2); const startIndented = !!params.config.start_indented; const startIndent = Number(params.config.start_indent || indent); - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { if (list.unordered && list.parentsUnordered) { - list.items.forEach((item) => { + for (const item of list.items) { const { lineNumber, line } = item; const expectedIndent = (startIndented ? startIndent : 0) + @@ -42,8 +42,8 @@ module.exports = { "deleteCount": actualIndent, "insertText": "".padEnd(expectedIndent) }); - }); + } } - }); + } } }; diff --git a/lib/md013.js b/lib/md013.js index a9a1b354..cf506ce0 100644 --- a/lib/md013.js +++ b/lib/md013.js @@ -58,11 +58,11 @@ module.exports = { const linkOnlyLineNumbers = []; filterTokens(params, "inline", (token) => { let childTokenTypes = ""; - token.children.forEach((child) => { + for (const child of token.children) { if (child.type !== "text" || child.content !== "") { childTokenTypes += tokenTypeMap[child.type] || "x"; } - }); + } if (linkOrImageOnlyLineRe.test(childTokenTypes)) { linkOnlyLineNumbers.push(token.lineNumber); } diff --git a/lib/md014.js b/lib/md014.js index 8dac5909..212de37b 100644 --- a/lib/md014.js +++ b/lib/md014.js @@ -11,7 +11,7 @@ module.exports = { "description": "Dollar signs used before commands without showing output", "tags": [ "code" ], "function": function MD014(params, onError) { - [ "code_block", "fence" ].forEach((type) => { + for (const type of [ "code_block", "fence" ]) { filterTokens(params, type, (token) => { const margin = (token.type === "fence") ? 1 : 0; const dollarInstances = []; @@ -31,7 +31,7 @@ module.exports = { } } if (allDollars) { - dollarInstances.forEach((instance) => { + for (const instance of dollarInstances) { const [ i, lineTrim, column, length ] = instance; addErrorContext( onError, @@ -45,9 +45,9 @@ module.exports = { "deleteCount": length } ); - }); + } } }); - }); + } } }; diff --git a/lib/md027.js b/lib/md027.js index ef56d3f1..e2e9de74 100644 --- a/lib/md027.js +++ b/lib/md027.js @@ -13,7 +13,7 @@ module.exports = { "function": function MD027(params, onError) { let blockquoteNesting = 0; let listItemNesting = 0; - params.tokens.forEach((token) => { + for (const token of params.tokens) { const { content, lineNumber, type } = token; if (type === "blockquote_open") { blockquoteNesting++; @@ -51,6 +51,6 @@ module.exports = { } } } - }); + } } }; diff --git a/lib/md028.js b/lib/md028.js index baa77d72..e3fd544e 100644 --- a/lib/md028.js +++ b/lib/md028.js @@ -11,7 +11,7 @@ module.exports = { "function": function MD028(params, onError) { let prevToken = {}; let prevLineNumber = null; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { if ((token.type === "blockquote_open") && (prevToken.type === "blockquote_close")) { for ( @@ -25,6 +25,6 @@ module.exports = { if (token.type === "blockquote_open") { prevLineNumber = token.map[1] + 1; } - }); + } } }; diff --git a/lib/md029.js b/lib/md029.js index 4560e596..1fbec237 100644 --- a/lib/md029.js +++ b/lib/md029.js @@ -18,7 +18,8 @@ module.exports = { "tags": [ "ol" ], "function": function MD029(params, onError) { const style = String(params.config.style || "one_or_ordered"); - flattenedLists().filter((list) => !list.unordered).forEach((list) => { + const filteredLists = flattenedLists().filter((list) => !list.unordered); + for (const list of filteredLists) { const { items } = list; let current = 1; let incrementing = false; @@ -49,7 +50,7 @@ module.exports = { current = 1; } // Validate each list item marker - items.forEach((item) => { + for (const item of items) { const match = orderedListItemMarkerRe.exec(item.line); if (match) { addErrorDetailIf(onError, item.lineNumber, @@ -60,7 +61,7 @@ module.exports = { current++; } } - }); - }); + } + } } }; diff --git a/lib/md030.js b/lib/md030.js index 03258f2f..2a9e453d 100644 --- a/lib/md030.js +++ b/lib/md030.js @@ -14,13 +14,13 @@ module.exports = { const olSingle = Number(params.config.ol_single || 1); const ulMulti = Number(params.config.ul_multi || 1); const olMulti = Number(params.config.ol_multi || 1); - flattenedLists().forEach((list) => { + for (const list of flattenedLists()) { const lineCount = list.lastLineIndex - list.open.map[0]; const allSingle = lineCount === list.items.length; const expectedSpaces = list.unordered ? (allSingle ? ulSingle : ulMulti) : (allSingle ? olSingle : olMulti); - list.items.forEach((item) => { + for (const item of list.items) { const { line, lineNumber } = item; const match = /^[\s>]*\S+(\s*)/.exec(line); const [ { "length": matchLength }, { "length": actualSpaces } ] = match; @@ -44,7 +44,7 @@ module.exports = { fixInfo ); } - }); - }); + } + } } }; diff --git a/lib/md032.js b/lib/md032.js index 607f167a..ec7c0c1c 100644 --- a/lib/md032.js +++ b/lib/md032.js @@ -13,7 +13,8 @@ module.exports = { "tags": [ "bullet", "ul", "ol", "blank_lines" ], "function": function MD032(params, onError) { const { lines } = params; - flattenedLists().filter((list) => !list.nesting).forEach((list) => { + const filteredLists = flattenedLists().filter((list) => !list.nesting); + for (const list of filteredLists) { const firstIndex = list.open.map[0]; if (!isBlankLine(lines[firstIndex - 1])) { const line = lines[firstIndex]; @@ -45,6 +46,6 @@ module.exports = { "insertText": `${quotePrefix}\n` }); } - }); + } } }; diff --git a/lib/md034.js b/lib/md034.js index 1b3b4b3d..fd721a45 100644 --- a/lib/md034.js +++ b/lib/md034.js @@ -11,7 +11,7 @@ module.exports = { "function": function MD034(params, onError) { filterTokens(params, "inline", (token) => { let inLink = false; - token.children.forEach((child) => { + for (const child of token.children) { const { content, line, lineNumber, type } = child; let match = null; if (type === "link_open") { @@ -55,7 +55,7 @@ module.exports = { } } } - }); + } }); } }; diff --git a/lib/md036.js b/lib/md036.js index 25621980..a1497174 100644 --- a/lib/md036.js +++ b/lib/md036.js @@ -49,8 +49,8 @@ module.exports = { return base; } let state = base; - params.tokens.forEach(function forToken(token) { + for (const token of params.tokens) { state = state(token); - }); + } } }; diff --git a/lib/md039.js b/lib/md039.js index 791acfc5..8362eb26 100644 --- a/lib/md039.js +++ b/lib/md039.js @@ -18,7 +18,7 @@ module.exports = { let inLink = false; let linkText = ""; let lineIndex = 0; - children.forEach((child) => { + for (const child of children) { const { content, markup, type } = child; if (type === "link_open") { inLink = true; @@ -61,7 +61,7 @@ module.exports = { `${markup}${content}${markup}` : (content || markup); } - }); + } }); } }; diff --git a/lib/md042.js b/lib/md042.js index 1695542d..2029a3b8 100644 --- a/lib/md042.js +++ b/lib/md042.js @@ -14,15 +14,15 @@ module.exports = { let inLink = false; let linkText = ""; let emptyLink = false; - token.children.forEach(function forChild(child) { + for (const child of token.children) { if (child.type === "link_open") { inLink = true; linkText = ""; - child.attrs.forEach(function forAttr(attr) { + for (const attr of child.attrs) { if (attr[0] === "href" && (!attr[1] || (attr[1] === "#"))) { emptyLink = true; } - }); + } } else if (child.type === "link_close") { inLink = false; if (emptyLink) { @@ -43,7 +43,7 @@ module.exports = { } else if (inLink) { linkText += child.content; } - }); + } }); } }; diff --git a/lib/md043.js b/lib/md043.js index 37b3cbd2..9b861155 100644 --- a/lib/md043.js +++ b/lib/md043.js @@ -13,9 +13,9 @@ module.exports = { const requiredHeadings = params.config.headings || params.config.headers; if (Array.isArray(requiredHeadings)) { const levels = {}; - [ 1, 2, 3, 4, 5, 6 ].forEach((level) => { + for (const level of [ 1, 2, 3, 4, 5, 6 ]) { levels["h" + level] = "######".substr(-level); - }); + } let i = 0; let matchAny = false; let hasError = false; diff --git a/lib/md046.js b/lib/md046.js index 2b492ab8..a87bd867 100644 --- a/lib/md046.js +++ b/lib/md046.js @@ -15,18 +15,19 @@ module.exports = { "tags": [ "code" ], "function": function MD046(params, onError) { let expectedStyle = String(params.config.style || "consistent"); - params.tokens - .filter((token) => token.type === "code_block" || token.type === "fence") - .forEach((token) => { - const { lineNumber, type } = token; - if (expectedStyle === "consistent") { - expectedStyle = tokenTypeToStyle[type]; - } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - tokenTypeToStyle[type]); - }); + const codeBlocksAndFences = params.tokens.filter( + (token) => (token.type === "code_block") || (token.type === "fence") + ); + for (const token of codeBlocksAndFences) { + const { lineNumber, type } = token; + if (expectedStyle === "consistent") { + expectedStyle = tokenTypeToStyle[type]; + } + addErrorDetailIf( + onError, + lineNumber, + expectedStyle, + tokenTypeToStyle[type]); + } } }; diff --git a/lib/md048.js b/lib/md048.js index cf8ac39d..be5cac2c 100644 --- a/lib/md048.js +++ b/lib/md048.js @@ -11,19 +11,18 @@ module.exports = { "function": function MD048(params, onError) { const style = String(params.config.style || "consistent"); let expectedStyle = style; - params.tokens - .filter((token) => token.type === "fence") - .forEach((fenceToken) => { - const { lineNumber, markup } = fenceToken; - if (expectedStyle === "consistent") { - expectedStyle = fencedCodeBlockStyleFor(markup); - } - addErrorDetailIf( - onError, - lineNumber, - expectedStyle, - fencedCodeBlockStyleFor(markup) - ); - }); + const fenceTokens = params.tokens.filter((token) => token.type === "fence"); + for (const fenceToken of fenceTokens) { + const { lineNumber, markup } = fenceToken; + if (expectedStyle === "consistent") { + expectedStyle = fencedCodeBlockStyleFor(markup); + } + addErrorDetailIf( + onError, + lineNumber, + expectedStyle, + fencedCodeBlockStyleFor(markup) + ); + } } }; diff --git a/lib/rules.js b/lib/rules.js index 193586b5..f6afe264 100644 --- a/lib/rules.js +++ b/lib/rules.js @@ -54,10 +54,10 @@ const rules = [ require("./md052"), require("./md053") ]; -rules.forEach((rule) => { +for (const rule of rules) { const name = rule.names[0].toLowerCase(); // eslint-disable-next-line dot-notation rule["information"] = new URL(`${homepage}/blob/v${version}/doc/Rules.md#${name}`); -}); +} module.exports = rules; diff --git a/package.json b/package.json index 56dae944..73c55ae6 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "build-config": "npm run build-config-schema && npm run build-config-example", "build-config-example": "node schema/build-config-example.js", "build-config-schema": "node schema/build-config-schema.js", - "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --resolveJsonModule lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", + "build-declaration": "tsc --allowJs --declaration --emitDeclarationOnly --module commonjs --resolveJsonModule --target es2015 lib/markdownlint.js && node scripts delete 'lib/{c,md,r}*.d.ts' 'helpers/*.d.ts'", "build-demo": "node scripts copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats", "build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2", "ci": "npm-run-all --continue-on-error --parallel build-config lint serial-declaration-demo test-cover && git diff --exit-code", diff --git a/schema/build-config-schema.js b/schema/build-config-schema.js index fea4ab4c..6594eb16 100644 --- a/schema/build-config-schema.js +++ b/schema/build-config-schema.js @@ -41,12 +41,12 @@ const schema = { const tags = {}; // Add rules -rules.forEach(function forRule(rule) { - rule.tags.forEach(function forTag(tag) { +for (const rule of rules) { + for (const tag of rule.tags) { const tagRules = tags[tag] || []; tagRules.push(rule.names[0]); tags[tag] = tagRules; - }); + } const scheme = { "description": rule.names.join("/") + " - " + rule.description, "type": "boolean", @@ -472,22 +472,22 @@ rules.forEach(function forRule(rule) { scheme.type = [ "boolean", "object" ]; scheme.additionalProperties = false; } - rule.names.forEach(function forName(name, index) { + for (const [ index, name ] of rule.names.entries()) { schema.properties[name] = (index === 0) ? scheme : { "$ref": `#/properties/${rule.names[0]}` }; - }); -}); + } +} // Add tags -Object.keys(tags).forEach(function forTag(tag) { +for (const [ tag, tagTags ] of Object.entries(tags)) { const scheme = { - "description": tag + " - " + tags[tag].join(", "), + "description": tag + " - " + tagTags.join(", "), "type": "boolean", "default": true }; schema.properties[tag] = scheme; -}); +} // Write schema const schemaFile = path.join(__dirname, "markdownlint-config-schema.json"); diff --git a/test/markdownlint-test-custom-rules.js b/test/markdownlint-test-custom-rules.js index 9d5eae08..286c1933 100644 --- a/test/markdownlint-test-custom-rules.js +++ b/test/markdownlint-test-custom-rules.js @@ -350,7 +350,7 @@ test.cb("customRulesNpmPackage", (t) => { test("customRulesBadProperty", (t) => { t.plan(27); - [ + for (const testCase of [ { "propertyName": "names", "propertyValues": @@ -377,9 +377,9 @@ test("customRulesBadProperty", (t) => { "propertyName": "function", "propertyValues": [ null, "string", [] ] } - ].forEach(function forTestCase(testCase) { + ]) { const { propertyName, propertyValues } = testCase; - propertyValues.forEach(function forPropertyValue(propertyValue) { + for (const propertyValue of propertyValues) { const badRule = { ...customRules.anyBlockquote }; badRule[propertyName] = propertyValue; const options = { @@ -395,8 +395,8 @@ test("customRulesBadProperty", (t) => { }, "Did not get correct exception for missing property." ); - }); - }); + } + } }); test.cb("customRulesUsedNameName", (t) => { @@ -639,7 +639,7 @@ test("customRulesOnErrorNullSync", (t) => { test("customRulesOnErrorBad", (t) => { t.plan(21); - [ + for (const testCase of [ { "propertyName": "lineNumber", "subPropertyName": null, @@ -685,9 +685,9 @@ test("customRulesOnErrorBad", (t) => { "subPropertyName": "insertText", "propertyValues": [ 10, [] ] } - ].forEach(function forTestCase(testCase) { + ]) { const { propertyName, subPropertyName, propertyValues } = testCase; - propertyValues.forEach(function forPropertyValue(propertyValue) { + for (const propertyValue of propertyValues) { const badObject = { "lineNumber": 1 }; @@ -725,13 +725,13 @@ test("customRulesOnErrorBad", (t) => { }, "Did not get correct exception for bad object." ); - }); - }); + } + } }); test("customRulesOnErrorInvalid", (t) => { t.plan(17); - [ + for (const testCase of [ { "propertyName": "lineNumber", "subPropertyName": null, @@ -757,9 +757,9 @@ test("customRulesOnErrorInvalid", (t) => { "subPropertyName": "deleteCount", "propertyValues": [ -2, 5 ] } - ].forEach(function forTestCase(testCase) { + ]) { const { propertyName, subPropertyName, propertyValues } = testCase; - propertyValues.forEach(function forPropertyValue(propertyValue) { + for (const propertyValue of propertyValues) { const badObject = { "lineNumber": 1 }; @@ -797,13 +797,13 @@ test("customRulesOnErrorInvalid", (t) => { }, "Did not get correct exception for invalid object." ); - }); - }); + } + } }); test("customRulesOnErrorValid", (t) => { t.plan(24); - [ + for (const testCase of [ { "propertyName": "lineNumber", "subPropertyName": null, @@ -835,9 +835,9 @@ test("customRulesOnErrorValid", (t) => { "propertyValues": [ "", "1", "123456", "\n", "\nText", "Text\n", "\nText\n" ] } - ].forEach(function forTestCase(testCase) { + ]) { const { propertyName, subPropertyName, propertyValues } = testCase; - propertyValues.forEach(function forPropertyValue(propertyValue) { + for (const propertyValue of propertyValues) { const goodObject = { "lineNumber": 1 }; @@ -864,8 +864,8 @@ test("customRulesOnErrorValid", (t) => { }; markdownlint.sync(options); t.truthy(true); - }); - }); + } + } }); test.cb("customRulesOnErrorLazy", (t) => { @@ -1424,7 +1424,7 @@ const stringScenarios = [ ] ]; -[ +for (const flavor of [ [ "customRulesThrowString", () => { @@ -1437,7 +1437,7 @@ const stringScenarios = [ throw new Error(errorMessage); } ] -].forEach((flavor) => { +]) { const [ name, func ] = flavor; const customRule = [ { @@ -1461,7 +1461,7 @@ const stringScenarios = [ } ] }; - stringScenarios.forEach((inputs) => { + for (const inputs of stringScenarios) { const [ subname, files, strings ] = inputs; test.cb(`${name}${subname}UnhandledAsync`, (t) => { @@ -1530,10 +1530,10 @@ const stringScenarios = [ }); t.deepEqual(actualResult, expectedResult, "Undetected issues."); }); - }); -}); + } +} -[ +for (const flavor of [ [ "customRulesAsyncExceptionString", () => { @@ -1570,7 +1570,7 @@ const stringScenarios = [ "customRulesAsyncRejectError", () => Promise.reject(new Error(errorMessage)) ] -].forEach((flavor) => { +]) { const [ name, func ] = flavor; const customRule = { "names": [ "name" ], @@ -1579,7 +1579,7 @@ const stringScenarios = [ "asynchronous": true, "function": func }; - stringScenarios.forEach((inputs) => { + for (const inputs of stringScenarios) { const [ subname, files, strings ] = inputs; test.cb(`${name}${subname}Unhandled`, (t) => { @@ -1630,5 +1630,5 @@ const stringScenarios = [ t.end(); }); }); - }); -}); + } +} diff --git a/test/markdownlint-test-extra-type.js b/test/markdownlint-test-extra-type.js index 827957aa..4fd0a24a 100644 --- a/test/markdownlint-test-extra-type.js +++ b/test/markdownlint-test-extra-type.js @@ -8,8 +8,10 @@ const test = require("ava").default; const markdownlint = require("../lib/markdownlint"); // Simulates typing each test file to validate handling of partial input -const files = fs.readdirSync("./test"); -files.filter((file) => /\.md$/.test(file)).forEach((file) => { +const files = fs + .readdirSync("./test") + .filter((file) => /\.md$/.test(file)); +for (const file of files) { const strings = {}; let content = fs.readFileSync(path.join("./test", file), "utf8"); while (content) { @@ -25,4 +27,4 @@ files.filter((file) => /\.md$/.test(file)).forEach((file) => { }); t.pass(); }); -}); +} diff --git a/test/markdownlint-test-helpers.js b/test/markdownlint-test-helpers.js index fc034225..af5a255d 100644 --- a/test/markdownlint-test-helpers.js +++ b/test/markdownlint-test-helpers.js @@ -219,11 +219,11 @@ bar` "_" ] ]; - testCases.forEach(function forTestCase(testCase) { + for (const testCase of testCases) { const [ markdown, expected, replacement ] = testCase; const actual = helpers.unescapeMarkdown(markdown, replacement); t.is(actual, expected); - }); + } }); test("isBlankLine", (t) => { @@ -253,7 +253,9 @@ test("isBlankLine", (t) => { "text --> --> text --> text