markdownlint
Advanced tools
Comparing version 0.16.0 to 0.17.0
@@ -46,3 +46,4 @@ # Custom Rules | ||
- `name` is a `String` that identifies the input file/string. | ||
- `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token) with added `line` and `lineNumber` properties. | ||
- `tokens` is an `Array` of [`markdown-it` `Token` objects](https://markdown-it.github.io/markdown-it/#Token) | ||
with added `line` and `lineNumber` properties. | ||
- `lines` is an `Array` of `String` values corresponding to the lines of the input file/string. | ||
@@ -56,2 +57,10 @@ - `frontMatterLines` is an `Array` of `String` values corresponding to any front matter (not present in `lines`). | ||
- `range` is an optional `Array` with two `Number` values identifying the 1-based column and length of the error. | ||
- `fixInfo` is an optional `Object` with information about how to fix the error (all properties are optional, but | ||
at least one of `deleteCount` and `insertText` should be present; when applying a fix, the delete should be | ||
performed before the insert): | ||
- `lineNumber` is an optional `Number` specifying the 1-based line number of the edit. | ||
- `editColumn` is an optional `Number` specifying the 1-based column number of the edit. | ||
- `deleteCount` is an optional `Number` specifying the count of characters to delete. | ||
- `insertText` is an optional `String` specifying the text to insert. `\n` is the platform-independent way to add | ||
a line break; line breaks should be added at the beginning of a line instead of at the end). | ||
@@ -58,0 +67,0 @@ The collection of helper functions shared by the built-in rules is available for use by custom rules in the [markdownlint-rule-helpers package](https://www.npmjs.com/package/markdownlint-rule-helpers). |
@@ -1098,13 +1098,2 @@ | ||
Note: List items without hanging indents are a violation of this rule; list | ||
items with hanging indents are okay: | ||
```markdown | ||
* This is | ||
not okay | ||
* This is | ||
okay | ||
``` | ||
<a name="md033"></a> | ||
@@ -1111,0 +1100,0 @@ |
@@ -5,5 +5,8 @@ // @ts-check | ||
const os = require("os"); | ||
// Regular expression for matching common newline characters | ||
// See NEWLINES_RE in markdown-it/lib/rules_core/normalize.js | ||
module.exports.newLineRe = /\r[\n\u0085]?|[\n\u2424\u2028\u0085]/; | ||
const newLineRe = /\r\n?|\n/g; | ||
module.exports.newLineRe = newLineRe; | ||
@@ -22,4 +25,3 @@ // Regular expression for matching common front matter (YAML and TOML) | ||
// Regular expressions for range matching | ||
module.exports.atxHeadingSpaceRe = /^#+\s*\S/; | ||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/i; | ||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/ig; | ||
module.exports.listItemMarkerRe = /^[\s>]*(?:[*+-]|\d+[.)])\s+/; | ||
@@ -49,2 +51,7 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/; | ||
// Returns true iff the input is an object | ||
module.exports.isObject = function isObject(obj) { | ||
return (obj !== null) && (typeof obj === "object") && !Array.isArray(obj); | ||
}; | ||
// Returns true iff the input line is blank (no content) | ||
@@ -318,3 +325,4 @@ // Example: Contains nothing, whitespace, or comments | ||
} else if ((char === "\\") && | ||
((startIndex === -1) || (startColumn === -1))) { | ||
((startIndex === -1) || (startColumn === -1)) && | ||
(input[index + 1] !== "\n")) { | ||
// Escape character outside code, skip next | ||
@@ -338,8 +346,9 @@ index++; | ||
// Adds a generic error object via the onError callback | ||
function addError(onError, lineNumber, detail, context, range) { | ||
function addError(onError, lineNumber, detail, context, range, fixInfo) { | ||
onError({ | ||
"lineNumber": lineNumber, | ||
"detail": detail, | ||
"context": context, | ||
"range": range | ||
lineNumber, | ||
detail, | ||
context, | ||
range, | ||
fixInfo | ||
}); | ||
@@ -351,3 +360,3 @@ } | ||
module.exports.addErrorDetailIf = function addErrorDetailIf( | ||
onError, lineNumber, expected, actual, detail, context, range) { | ||
onError, lineNumber, expected, actual, detail, context, range, fixInfo) { | ||
if (expected !== actual) { | ||
@@ -360,3 +369,4 @@ addError( | ||
context, | ||
range); | ||
range, | ||
fixInfo); | ||
} | ||
@@ -366,15 +376,15 @@ }; | ||
// Adds an error object with context via the onError callback | ||
module.exports.addErrorContext = | ||
function addErrorContext(onError, lineNumber, context, left, right, range) { | ||
if (context.length <= 30) { | ||
// Nothing to do | ||
} else if (left && right) { | ||
context = context.substr(0, 15) + "..." + context.substr(-15); | ||
} else if (right) { | ||
context = "..." + context.substr(-30); | ||
} else { | ||
context = context.substr(0, 30) + "..."; | ||
} | ||
addError(onError, lineNumber, null, context, range); | ||
}; | ||
module.exports.addErrorContext = function addErrorContext( | ||
onError, lineNumber, context, left, right, range, fixInfo) { | ||
if (context.length <= 30) { | ||
// Nothing to do | ||
} else if (left && right) { | ||
context = context.substr(0, 15) + "..." + context.substr(-15); | ||
} else if (right) { | ||
context = "..." + context.substr(-30); | ||
} else { | ||
context = context.substr(0, 30) + "..."; | ||
} | ||
addError(onError, lineNumber, null, context, range, fixInfo); | ||
}; | ||
@@ -407,1 +417,125 @@ // Returns a range object for a line by applying a RegExp | ||
}; | ||
// Gets the most common line ending, falling back to platform default | ||
function getPreferredLineEnding(input) { | ||
let cr = 0; | ||
let lf = 0; | ||
let crlf = 0; | ||
const endings = input.match(newLineRe) || []; | ||
endings.forEach((ending) => { | ||
// eslint-disable-next-line default-case | ||
switch (ending) { | ||
case "\r": | ||
cr++; | ||
break; | ||
case "\n": | ||
lf++; | ||
break; | ||
case "\r\n": | ||
crlf++; | ||
break; | ||
} | ||
}); | ||
let preferredLineEnding = null; | ||
if (!cr && !lf && !crlf) { | ||
preferredLineEnding = os.EOL; | ||
} else if ((lf >= crlf) && (lf >= cr)) { | ||
preferredLineEnding = "\n"; | ||
} else if (crlf >= cr) { | ||
preferredLineEnding = "\r\n"; | ||
} else { | ||
preferredLineEnding = "\r"; | ||
} | ||
return preferredLineEnding; | ||
} | ||
module.exports.getPreferredLineEnding = getPreferredLineEnding; | ||
// Normalizes the fields of a fixInfo object | ||
function normalizeFixInfo(fixInfo, lineNumber) { | ||
return { | ||
"lineNumber": fixInfo.lineNumber || lineNumber, | ||
"editColumn": fixInfo.editColumn || 1, | ||
"deleteCount": fixInfo.deleteCount || 0, | ||
"insertText": fixInfo.insertText || "" | ||
}; | ||
} | ||
// Fixes the specifide error on a line | ||
function applyFix(line, fixInfo, lineEnding) { | ||
const { editColumn, deleteCount, insertText } = normalizeFixInfo(fixInfo); | ||
const editIndex = editColumn - 1; | ||
return (deleteCount === -1) ? | ||
null : | ||
line.slice(0, editIndex) + | ||
insertText.replace(/\n/g, lineEnding || "\n") + | ||
line.slice(editIndex + deleteCount); | ||
} | ||
module.exports.applyFix = applyFix; | ||
// Applies as many fixes as possible to the input lines | ||
module.exports.applyFixes = function applyFixes(input, errors) { | ||
const lineEnding = getPreferredLineEnding(input); | ||
const lines = input.split(newLineRe); | ||
// Normalize fixInfo objects | ||
let fixInfos = errors | ||
.filter((error) => error.fixInfo) | ||
.map((error) => normalizeFixInfo(error.fixInfo, error.lineNumber)); | ||
// Sort bottom-to-top, line-deletes last, right-to-left, long-to-short | ||
fixInfos.sort((a, b) => { | ||
const aDeletingLine = (a.deleteCount === -1); | ||
const bDeletingLine = (b.deleteCount === -1); | ||
return ( | ||
(b.lineNumber - a.lineNumber) || | ||
(aDeletingLine ? 1 : (bDeletingLine ? -1 : 0)) || | ||
(b.editColumn - a.editColumn) || | ||
(b.insertText.length - a.insertText.length) | ||
); | ||
}); | ||
// Remove duplicate entries (needed for following collapse step) | ||
let lastFixInfo = {}; | ||
fixInfos = fixInfos.filter((fixInfo) => { | ||
const unique = ( | ||
(fixInfo.lineNumber !== lastFixInfo.lineNumber) || | ||
(fixInfo.editColumn !== lastFixInfo.editColumn) || | ||
(fixInfo.deleteCount !== lastFixInfo.deleteCount) || | ||
(fixInfo.insertText !== lastFixInfo.insertText) | ||
); | ||
lastFixInfo = fixInfo; | ||
return unique; | ||
}); | ||
// Collapse insert/no-delete and no-insert/delete for same line/column | ||
lastFixInfo = {}; | ||
fixInfos.forEach((fixInfo) => { | ||
if ( | ||
(fixInfo.lineNumber === lastFixInfo.lineNumber) && | ||
(fixInfo.editColumn === lastFixInfo.editColumn) && | ||
!fixInfo.insertText && | ||
(fixInfo.deleteCount > 0) && | ||
lastFixInfo.insertText && | ||
!lastFixInfo.deleteCount) { | ||
fixInfo.insertText = lastFixInfo.insertText; | ||
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) => { | ||
const { lineNumber, editColumn, deleteCount } = fixInfo; | ||
const lineIndex = lineNumber - 1; | ||
const editIndex = editColumn - 1; | ||
if ( | ||
(lineIndex !== lastLineIndex) || | ||
((editIndex + deleteCount) < lastEditIndex) || | ||
(deleteCount === -1) | ||
) { | ||
lines[lineIndex] = applyFix(lines[lineIndex], fixInfo, lineEnding); | ||
} | ||
lastLineIndex = lineIndex; | ||
lastEditIndex = editIndex; | ||
}); | ||
// Return corrected input | ||
return lines.filter((line) => line !== null).join(lineEnding); | ||
}; |
{ | ||
"name": "markdownlint-rule-helpers", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "A collection of markdownlint helper functions for custom rules", | ||
@@ -5,0 +5,0 @@ "main": "helpers.js", |
@@ -175,2 +175,9 @@ // @ts-check | ||
let lineNumber = token.lineNumber; | ||
const codeSpanExtraLines = []; | ||
helpers.forEachInlineCodeSpan( | ||
token.content, | ||
function handleInlineCodeSpan(code) { | ||
codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); | ||
} | ||
); | ||
(token.children || []).forEach(function forChild(child) { | ||
@@ -181,2 +188,4 @@ child.lineNumber = lineNumber; | ||
lineNumber++; | ||
} else if (child.type === "code_inline") { | ||
lineNumber += codeSpanExtraLines.shift(); | ||
} | ||
@@ -301,2 +310,7 @@ }); | ||
// Function to return true for all inputs | ||
function filterAllValues() { | ||
return true; | ||
} | ||
// Function to return unique values from a sorted errors array | ||
@@ -383,2 +397,33 @@ function uniqueFilterForSortedErrors(value, index, array) { | ||
} | ||
const fixInfo = errorInfo.fixInfo; | ||
if (fixInfo) { | ||
if (!helpers.isObject(fixInfo)) { | ||
throwError("fixInfo"); | ||
} | ||
if ((fixInfo.lineNumber !== undefined) && | ||
(!helpers.isNumber(fixInfo.lineNumber) || | ||
(fixInfo.lineNumber < 1) || | ||
(fixInfo.lineNumber > lines.length))) { | ||
throwError("fixInfo.lineNumber"); | ||
} | ||
const effectiveLineNumber = fixInfo.lineNumber || errorInfo.lineNumber; | ||
if ((fixInfo.editColumn !== undefined) && | ||
(!helpers.isNumber(fixInfo.editColumn) || | ||
(fixInfo.editColumn < 1) || | ||
(fixInfo.editColumn > | ||
lines[effectiveLineNumber - 1].length + 1))) { | ||
throwError("fixInfo.editColumn"); | ||
} | ||
if ((fixInfo.deleteCount !== undefined) && | ||
(!helpers.isNumber(fixInfo.deleteCount) || | ||
(fixInfo.deleteCount < -1) || | ||
(fixInfo.deleteCount > | ||
lines[effectiveLineNumber - 1].length))) { | ||
throwError("fixInfo.deleteCount"); | ||
} | ||
if ((fixInfo.insertText !== undefined) && | ||
!helpers.isString(fixInfo.insertText)) { | ||
throwError("fixInfo.insertText"); | ||
} | ||
} | ||
errors.push({ | ||
@@ -388,3 +433,4 @@ "lineNumber": errorInfo.lineNumber + frontMatterLines.length, | ||
"context": errorInfo.context || null, | ||
"range": errorInfo.range || null | ||
"range": errorInfo.range || null, | ||
"fixInfo": errorInfo.fixInfo || null | ||
}); | ||
@@ -409,3 +455,5 @@ } | ||
const filteredErrors = errors | ||
.filter(uniqueFilterForSortedErrors) | ||
.filter((resultVersion === 3) ? | ||
filterAllValues : | ||
uniqueFilterForSortedErrors) | ||
.filter(function removeDisabledRules(error) { | ||
@@ -432,2 +480,5 @@ return enabledRulesPerLineNumber[error.lineNumber][ruleName]; | ||
errorObject.errorRange = error.range; | ||
if (resultVersion === 3) { | ||
errorObject.fixInfo = error.fixInfo; | ||
} | ||
return errorObject; | ||
@@ -434,0 +485,0 @@ }); |
@@ -16,6 +16,15 @@ // @ts-check | ||
flattenedLists().forEach((list) => { | ||
if (list.unordered && !list.nesting) { | ||
addErrorDetailIf(onError, list.open.lineNumber, | ||
0, list.indent, null, null, | ||
rangeFromRegExp(list.open.line, listItemMarkerRe)); | ||
if (list.unordered && !list.nesting && (list.indent !== 0)) { | ||
const { lineNumber, line } = list.open; | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
0, | ||
list.indent, | ||
null, | ||
null, | ||
rangeFromRegExp(line, listItemMarkerRe), | ||
{ | ||
"deleteCount": line.length - line.trimLeft().length | ||
}); | ||
} | ||
@@ -22,0 +31,0 @@ }); |
@@ -5,8 +5,6 @@ // @ts-check | ||
const { addError, filterTokens, forEachLine, includesSorted, rangeFromRegExp } = | ||
const { addError, filterTokens, forEachLine, includesSorted } = | ||
require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
const trailingSpaceRe = /\s+$/; | ||
module.exports = { | ||
@@ -38,10 +36,18 @@ "names": [ "MD009", "no-trailing-spaces" ], | ||
const lineNumber = lineIndex + 1; | ||
if ((!inCode || inFencedCode) && trailingSpaceRe.test(line) && | ||
const trailingSpaces = line.length - line.trimRight().length; | ||
if ((!inCode || inFencedCode) && trailingSpaces && | ||
!includesSorted(listItemLineNumbers, lineNumber)) { | ||
const actual = line.length - line.trimRight().length; | ||
if (expected !== actual) { | ||
addError(onError, lineNumber, | ||
if (expected !== trailingSpaces) { | ||
const column = line.length - trailingSpaces + 1; | ||
addError( | ||
onError, | ||
lineNumber, | ||
"Expected: " + (expected === 0 ? "" : "0 or ") + | ||
expected + "; Actual: " + actual, | ||
null, rangeFromRegExp(line, trailingSpaceRe)); | ||
expected + "; Actual: " + trailingSpaces, | ||
null, | ||
[ column, trailingSpaces ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": trailingSpaces | ||
}); | ||
} | ||
@@ -48,0 +54,0 @@ } |
@@ -5,6 +5,6 @@ // @ts-check | ||
const { addError, forEachLine, rangeFromRegExp } = require("../helpers"); | ||
const { addError, forEachLine } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
const tabRe = /\t+/; | ||
const tabRe = /\t+/g; | ||
@@ -19,5 +19,19 @@ module.exports = { | ||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => { | ||
if (tabRe.test(line) && (!inCode || includeCodeBlocks)) { | ||
addError(onError, lineIndex + 1, "Column: " + (line.indexOf("\t") + 1), | ||
null, rangeFromRegExp(line, tabRe)); | ||
if (!inCode || includeCodeBlocks) { | ||
let match = null; | ||
while ((match = tabRe.exec(line)) !== null) { | ||
const column = match.index + 1; | ||
const length = match[0].length; | ||
addError( | ||
onError, | ||
lineIndex + 1, | ||
"Column: " + column, | ||
null, | ||
[ column, length ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": length, | ||
"insertText": "".padEnd(length) | ||
}); | ||
} | ||
} | ||
@@ -24,0 +38,0 @@ }); |
@@ -5,5 +5,6 @@ // @ts-check | ||
const { addError, forEachInlineChild, rangeFromRegExp } = require("../helpers"); | ||
const { addError, forEachInlineChild, unescapeMarkdown } = | ||
require("../helpers"); | ||
const reversedLinkRe = /\([^)]+\)\[[^\]^][^\]]*]/; | ||
const reversedLinkRe = /\(([^)]+)\)\[([^\]^][^\]]*)]/g; | ||
@@ -15,7 +16,22 @@ module.exports = { | ||
"function": function MD011(params, onError) { | ||
forEachInlineChild(params, "text", function forToken(token) { | ||
const match = reversedLinkRe.exec(token.content); | ||
if (match) { | ||
addError(onError, token.lineNumber, match[0], null, | ||
rangeFromRegExp(token.line, reversedLinkRe)); | ||
forEachInlineChild(params, "text", (token) => { | ||
const { lineNumber, content } = token; | ||
let match = null; | ||
while ((match = reversedLinkRe.exec(content)) !== null) { | ||
const [ reversedLink, linkText, linkDestination ] = match; | ||
const line = params.lines[lineNumber - 1]; | ||
const column = unescapeMarkdown(line).indexOf(reversedLink) + 1; | ||
const length = reversedLink.length; | ||
addError( | ||
onError, | ||
lineNumber, | ||
reversedLink, | ||
null, | ||
[ column, length ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": length, | ||
"insertText": `[${linkText}](${linkDestination})` | ||
} | ||
); | ||
} | ||
@@ -22,0 +38,0 @@ }); |
@@ -18,3 +18,13 @@ // @ts-check | ||
if (maximum < count) { | ||
addErrorDetailIf(onError, lineIndex + 1, maximum, count); | ||
addErrorDetailIf( | ||
onError, | ||
lineIndex + 1, | ||
maximum, | ||
count, | ||
null, | ||
null, | ||
null, | ||
{ | ||
"deleteCount": -1 | ||
}); | ||
} | ||
@@ -21,0 +31,0 @@ }); |
@@ -5,7 +5,24 @@ // @ts-check | ||
const { addErrorContext, filterTokens, newLineRe, rangeFromRegExp } = | ||
require("../helpers"); | ||
const { addErrorContext, filterTokens } = require("../helpers"); | ||
const dollarCommandRe = /^(\s*)(\$\s)/; | ||
const dollarCommandRe = /^(\s*)(\$\s+)/; | ||
function addErrorIfPreviousWasCommand(onError, previous) { | ||
if (previous) { | ||
const { lineNumber, lineTrim, column, length } = previous; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
lineTrim, | ||
null, | ||
null, | ||
[ column, length ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": length | ||
} | ||
); | ||
} | ||
} | ||
module.exports = { | ||
@@ -16,13 +33,21 @@ "names": [ "MD014", "commands-show-output" ], | ||
"function": function MD014(params, onError) { | ||
[ "code_block", "fence" ].forEach(function forType(type) { | ||
filterTokens(params, type, function forToken(token) { | ||
let allBlank = true; | ||
if (token.content && token.content.split(newLineRe) | ||
.every(function forLine(line) { | ||
return !line || (allBlank = false) || dollarCommandRe.test(line); | ||
}) && !allBlank) { | ||
addErrorContext(onError, token.lineNumber, | ||
token.content.split(newLineRe)[0].trim(), null, null, | ||
rangeFromRegExp(token.line, dollarCommandRe)); | ||
[ "code_block", "fence" ].forEach((type) => { | ||
filterTokens(params, type, (token) => { | ||
let previous = null; | ||
const margin = (token.type === "fence") ? 1 : 0; | ||
for (let i = token.map[0] + margin; i < token.map[1] - margin; i++) { | ||
const line = params.lines[i]; | ||
const lineTrim = line.trim(); | ||
const match = dollarCommandRe.exec(line); | ||
if (!lineTrim || match) { | ||
addErrorIfPreviousWasCommand(onError, previous); | ||
} | ||
previous = match ? { | ||
"lineNumber": i + 1, | ||
"lineTrim": lineTrim, | ||
"column": match[1].length + 1, | ||
"length": match[2].length | ||
} : null; | ||
} | ||
addErrorIfPreviousWasCommand(onError, previous); | ||
}); | ||
@@ -29,0 +54,0 @@ }); |
@@ -5,4 +5,3 @@ // @ts-check | ||
const { addErrorContext, atxHeadingSpaceRe, forEachLine, | ||
rangeFromRegExp } = require("../helpers"); | ||
const { addErrorContext, forEachLine } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
@@ -16,5 +15,16 @@ | ||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => { | ||
if (!inCode && /^#+[^#\s]/.test(line) && !/#$/.test(line)) { | ||
addErrorContext(onError, lineIndex + 1, line.trim(), null, | ||
null, rangeFromRegExp(line, atxHeadingSpaceRe)); | ||
if (!inCode && /^#+[^#\s]/.test(line) && !/#\s*$/.test(line)) { | ||
const hashCount = /^#+/.exec(line)[0].length; | ||
addErrorContext( | ||
onError, | ||
lineIndex + 1, | ||
line.trim(), | ||
null, | ||
null, | ||
[ 1, hashCount + 1 ], | ||
{ | ||
"editColumn": hashCount + 1, | ||
"insertText": " " | ||
} | ||
); | ||
} | ||
@@ -21,0 +31,0 @@ }); |
@@ -5,4 +5,4 @@ // @ts-check | ||
const { addErrorContext, atxHeadingSpaceRe, filterTokens, headingStyleFor, | ||
rangeFromRegExp } = require("../helpers"); | ||
const { addErrorContext, filterTokens, headingStyleFor } = | ||
require("../helpers"); | ||
@@ -14,8 +14,25 @@ module.exports = { | ||
"function": function MD019(params, onError) { | ||
filterTokens(params, "heading_open", function forToken(token) { | ||
if ((headingStyleFor(token) === "atx") && | ||
/^#+\s\s/.test(token.line)) { | ||
addErrorContext(onError, token.lineNumber, token.line.trim(), | ||
null, null, | ||
rangeFromRegExp(token.line, atxHeadingSpaceRe)); | ||
filterTokens(params, "heading_open", (token) => { | ||
if (headingStyleFor(token) === "atx") { | ||
const { line, lineNumber } = token; | ||
const match = /^(#+)(\s{2,})(?:\S)/.exec(line); | ||
if (match) { | ||
const [ | ||
, | ||
{ "length": hashLength }, | ||
{ "length": spacesLength } | ||
] = match; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
line.trim(), | ||
null, | ||
null, | ||
[ 1, hashLength + spacesLength + 1 ], | ||
{ | ||
"editColumn": hashLength + 1, | ||
"deleteCount": spacesLength - 1 | ||
} | ||
); | ||
} | ||
} | ||
@@ -22,0 +39,0 @@ }); |
@@ -5,7 +5,5 @@ // @ts-check | ||
const { addErrorContext, forEachLine, rangeFromRegExp } = require("../helpers"); | ||
const { addErrorContext, forEachLine } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
const atxClosedHeadingNoSpaceRe = /(?:^#+[^#\s])|(?:[^#\s]#+\s*$)/; | ||
module.exports = { | ||
@@ -17,8 +15,46 @@ "names": [ "MD020", "no-missing-space-closed-atx" ], | ||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => { | ||
if (!inCode && /^#+[^#]*[^\\]#+$/.test(line)) { | ||
const left = /^#+[^#\s]/.test(line); | ||
const right = /[^#\s]#+$/.test(line); | ||
if (left || right) { | ||
addErrorContext(onError, lineIndex + 1, line.trim(), left, | ||
right, rangeFromRegExp(line, atxClosedHeadingNoSpaceRe)); | ||
if (!inCode) { | ||
const match = | ||
/^(#+)(\s*)([^#]+?[^#\\])(\s*)((?:\\#)?)(#+)(\s*)$/.exec(line); | ||
if (match) { | ||
const [ | ||
, | ||
leftHash, | ||
{ "length": leftSpaceLength }, | ||
content, | ||
{ "length": rightSpaceLength }, | ||
rightEscape, | ||
rightHash, | ||
{ "length": trailSpaceLength } | ||
] = match; | ||
const leftHashLength = leftHash.length; | ||
const rightHashLength = rightHash.length; | ||
const left = !leftSpaceLength; | ||
const right = !rightSpaceLength || rightEscape; | ||
const rightEscapeReplacement = rightEscape ? `${rightEscape} ` : ""; | ||
if (left || right) { | ||
const range = left ? | ||
[ | ||
1, | ||
leftHashLength + 1 | ||
] : | ||
[ | ||
line.length - trailSpaceLength - rightHashLength, | ||
rightHashLength + 1 | ||
]; | ||
addErrorContext( | ||
onError, | ||
lineIndex + 1, | ||
line.trim(), | ||
left, | ||
right, | ||
range, | ||
{ | ||
"editColumn": 1, | ||
"deleteCount": line.length, | ||
"insertText": | ||
`${leftHash} ${content} ${rightEscapeReplacement}${rightHash}` | ||
} | ||
); | ||
} | ||
} | ||
@@ -25,0 +61,0 @@ } |
@@ -5,7 +5,5 @@ // @ts-check | ||
const { addErrorContext, filterTokens, headingStyleFor, rangeFromRegExp } = | ||
const { addErrorContext, filterTokens, headingStyleFor } = | ||
require("../helpers"); | ||
const atxClosedHeadingSpaceRe = /(?:^#+\s\s+?\S)|(?:\S\s\s+?#+\s*$)/; | ||
module.exports = { | ||
@@ -16,10 +14,45 @@ "names": [ "MD021", "no-multiple-space-closed-atx" ], | ||
"function": function MD021(params, onError) { | ||
filterTokens(params, "heading_open", function forToken(token) { | ||
filterTokens(params, "heading_open", (token) => { | ||
if (headingStyleFor(token) === "atx_closed") { | ||
const left = /^#+\s\s/.test(token.line); | ||
const right = /\s\s#+$/.test(token.line); | ||
if (left || right) { | ||
addErrorContext(onError, token.lineNumber, token.line.trim(), | ||
left, right, | ||
rangeFromRegExp(token.line, atxClosedHeadingSpaceRe)); | ||
const { line, lineNumber } = token; | ||
const match = /^(#+)(\s+)([^#]+?)(\s+)(#+)(\s*)$/.exec(line); | ||
if (match) { | ||
const [ | ||
, | ||
leftHash, | ||
{ "length": leftSpaceLength }, | ||
content, | ||
{ "length": rightSpaceLength }, | ||
rightHash, | ||
{ "length": trailSpaceLength } | ||
] = match; | ||
const left = leftSpaceLength > 1; | ||
const right = rightSpaceLength > 1; | ||
if (left || right) { | ||
const length = line.length; | ||
const leftHashLength = leftHash.length; | ||
const rightHashLength = rightHash.length; | ||
const range = left ? | ||
[ | ||
1, | ||
leftHashLength + leftSpaceLength + 1 | ||
] : | ||
[ | ||
length - trailSpaceLength - rightHashLength - rightSpaceLength, | ||
rightSpaceLength + rightHashLength + 1 | ||
]; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
line.trim(), | ||
left, | ||
right, | ||
range, | ||
{ | ||
"editColumn": 1, | ||
"deleteCount": length, | ||
"insertText": `${leftHash} ${content} ${rightHash}` | ||
} | ||
); | ||
} | ||
} | ||
@@ -26,0 +59,0 @@ } |
@@ -23,18 +23,39 @@ // @ts-check | ||
const [ topIndex, nextIndex ] = token.map; | ||
let actualAbove = 0; | ||
for (let i = 0; i < linesAbove; i++) { | ||
if (!isBlankLine(lines[topIndex - i - 1])) { | ||
addErrorDetailIf(onError, topIndex + 1, linesAbove, i, "Above", | ||
lines[topIndex].trim()); | ||
return; | ||
if (isBlankLine(lines[topIndex - i - 1])) { | ||
actualAbove++; | ||
} | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
topIndex + 1, | ||
linesAbove, | ||
actualAbove, | ||
"Above", | ||
lines[topIndex].trim(), | ||
null, | ||
{ | ||
"insertText": "".padEnd(linesAbove - actualAbove, "\n") | ||
}); | ||
let actualBelow = 0; | ||
for (let i = 0; i < linesBelow; i++) { | ||
if (!isBlankLine(lines[nextIndex + i])) { | ||
addErrorDetailIf(onError, topIndex + 1, linesBelow, i, "Below", | ||
lines[topIndex].trim()); | ||
return; | ||
if (isBlankLine(lines[nextIndex + i])) { | ||
actualBelow++; | ||
} | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
topIndex + 1, | ||
linesBelow, | ||
actualBelow, | ||
"Below", | ||
lines[topIndex].trim(), | ||
null, | ||
{ | ||
"lineNumber": nextIndex + 1, | ||
"insertText": "".padEnd(linesBelow - actualBelow, "\n") | ||
}); | ||
}); | ||
} | ||
}; |
@@ -5,4 +5,3 @@ // @ts-check | ||
const { addErrorContext, filterTokens, rangeFromRegExp } = | ||
require("../helpers"); | ||
const { addErrorContext, filterTokens } = require("../helpers"); | ||
@@ -17,5 +16,22 @@ const spaceBeforeHeadingRe = /^((?:\s+)|(?:[>\s]+\s\s))[^>\s]/; | ||
filterTokens(params, "heading_open", function forToken(token) { | ||
if (spaceBeforeHeadingRe.test(token.line)) { | ||
addErrorContext(onError, token.lineNumber, token.line, null, | ||
null, rangeFromRegExp(token.line, spaceBeforeHeadingRe)); | ||
const { lineNumber, line } = token; | ||
const match = line.match(spaceBeforeHeadingRe); | ||
if (match) { | ||
const [ prefixAndFirstChar, prefix ] = match; | ||
let deleteCount = prefix.length; | ||
const prefixLengthNoSpace = prefix.trimRight().length; | ||
if (prefixLengthNoSpace) { | ||
deleteCount -= prefixLengthNoSpace - 1; | ||
} | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
line, | ||
null, | ||
null, | ||
[ 1, prefixAndFirstChar.length ], | ||
{ | ||
"editColumn": prefixLengthNoSpace + 1, | ||
"deleteCount": deleteCount | ||
}); | ||
} | ||
@@ -22,0 +38,0 @@ }); |
@@ -5,4 +5,4 @@ // @ts-check | ||
const { addError, allPunctuation, escapeForRegExp, forEachHeading, | ||
rangeFromRegExp } = require("../helpers"); | ||
const { addError, allPunctuation, escapeForRegExp, forEachHeading } = | ||
require("../helpers"); | ||
@@ -19,9 +19,22 @@ module.exports = { | ||
const trailingPunctuationRe = | ||
new RegExp("[" + escapeForRegExp(punctuation) + "]$"); | ||
forEachHeading(params, (heading, content) => { | ||
const match = trailingPunctuationRe.exec(content); | ||
new RegExp("\\s*[" + escapeForRegExp(punctuation) + "]+$"); | ||
forEachHeading(params, (heading) => { | ||
const { line, lineNumber } = heading; | ||
const trimmedLine = line.replace(/[\s#]*$/, ""); | ||
const match = trailingPunctuationRe.exec(trimmedLine); | ||
if (match) { | ||
addError(onError, heading.lineNumber, | ||
"Punctuation: '" + match[0] + "'", null, | ||
rangeFromRegExp(heading.line, trailingPunctuationRe)); | ||
const fullMatch = match[0]; | ||
const column = match.index + 1; | ||
const length = fullMatch.length; | ||
addError( | ||
onError, | ||
lineNumber, | ||
`Punctuation: '${fullMatch}'`, | ||
null, | ||
[ column, length ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": length | ||
} | ||
); | ||
} | ||
@@ -28,0 +41,0 @@ }); |
@@ -5,5 +5,5 @@ // @ts-check | ||
const { addErrorContext, newLineRe, rangeFromRegExp } = require("../helpers"); | ||
const { addErrorContext, newLineRe } = require("../helpers"); | ||
const spaceAfterBlockQuote = /^\s*(?:>\s+)+\S/; | ||
const spaceAfterBlockQuoteRe = /^((?:\s*>)+)(\s{2,})\S/; | ||
@@ -17,27 +17,39 @@ module.exports = { | ||
let listItemNesting = 0; | ||
params.tokens.forEach(function forToken(token) { | ||
if (token.type === "blockquote_open") { | ||
params.tokens.forEach((token) => { | ||
const { content, lineNumber, type } = token; | ||
if (type === "blockquote_open") { | ||
blockquoteNesting++; | ||
} else if (token.type === "blockquote_close") { | ||
} else if (type === "blockquote_close") { | ||
blockquoteNesting--; | ||
} else if (token.type === "list_item_open") { | ||
} else if (type === "list_item_open") { | ||
listItemNesting++; | ||
} else if (token.type === "list_item_close") { | ||
} else if (type === "list_item_close") { | ||
listItemNesting--; | ||
} else if ((token.type === "inline") && (blockquoteNesting > 0)) { | ||
const multipleSpaces = listItemNesting ? | ||
/^(\s*>)+\s\s+>/.test(token.line) : | ||
/^(\s*>)+\s\s/.test(token.line); | ||
if (multipleSpaces) { | ||
addErrorContext(onError, token.lineNumber, token.line, null, | ||
null, rangeFromRegExp(token.line, spaceAfterBlockQuote)); | ||
} else if ((type === "inline") && blockquoteNesting) { | ||
const lineCount = content.split(newLineRe).length; | ||
for (let i = 0; i < lineCount; i++) { | ||
const line = params.lines[lineNumber + i - 1]; | ||
const match = line.match(spaceAfterBlockQuoteRe); | ||
if (match) { | ||
const [ | ||
fullMatch, | ||
{ "length": blockquoteLength }, | ||
{ "length": spaceLength } | ||
] = match; | ||
if (!listItemNesting || (fullMatch[fullMatch.length - 1] === ">")) { | ||
addErrorContext( | ||
onError, | ||
lineNumber + i, | ||
line, | ||
null, | ||
null, | ||
[ 1, fullMatch.length ], | ||
{ | ||
"editColumn": blockquoteLength + 1, | ||
"deleteCount": spaceLength - 1 | ||
} | ||
); | ||
} | ||
} | ||
} | ||
token.content.split(newLineRe) | ||
.forEach(function forLine(line, offset) { | ||
if (/^\s/.test(line)) { | ||
addErrorContext(onError, token.lineNumber + offset, | ||
"> " + line, null, null, | ||
rangeFromRegExp(line, spaceAfterBlockQuote)); | ||
} | ||
}); | ||
} | ||
@@ -44,0 +56,0 @@ }); |
@@ -13,10 +13,27 @@ // @ts-check | ||
let prevToken = {}; | ||
let prevLineNumber = null; | ||
params.tokens.forEach(function forToken(token) { | ||
if ((token.type === "blockquote_open") && | ||
(prevToken.type === "blockquote_close")) { | ||
addError(onError, token.lineNumber - 1); | ||
for ( | ||
let lineNumber = prevLineNumber; | ||
lineNumber < token.lineNumber; | ||
lineNumber++) { | ||
addError( | ||
onError, | ||
lineNumber, | ||
null, | ||
null, | ||
null, | ||
{ | ||
"deleteCount": -1 | ||
}); | ||
} | ||
} | ||
prevToken = token; | ||
if (token.type === "blockquote_open") { | ||
prevLineNumber = token.map[1] + 1; | ||
} | ||
}); | ||
} | ||
}; |
@@ -5,4 +5,3 @@ // @ts-check | ||
const { addErrorDetailIf, listItemMarkerRe, rangeFromRegExp } = | ||
require("../helpers"); | ||
const { addErrorDetailIf } = require("../helpers"); | ||
const { flattenedLists } = require("./cache"); | ||
@@ -26,6 +25,23 @@ | ||
list.items.forEach((item) => { | ||
const match = /^[\s>]*\S+(\s+)/.exec(item.line); | ||
addErrorDetailIf(onError, item.lineNumber, | ||
expectedSpaces, (match ? match[1].length : 0), null, null, | ||
rangeFromRegExp(item.line, listItemMarkerRe)); | ||
const { line, lineNumber } = item; | ||
const match = /^[\s>]*\S+(\s*)/.exec(line); | ||
const [ { "length": matchLength }, { "length": actualSpaces } ] = match; | ||
let fixInfo = null; | ||
if ((expectedSpaces !== actualSpaces) && (line.length > matchLength)) { | ||
fixInfo = { | ||
"editColumn": matchLength - actualSpaces + 1, | ||
"deleteCount": actualSpaces, | ||
"insertText": "".padEnd(expectedSpaces) | ||
}; | ||
} | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
expectedSpaces, | ||
actualSpaces, | ||
null, | ||
null, | ||
[ 1, matchLength ], | ||
fixInfo | ||
); | ||
}); | ||
@@ -32,0 +48,0 @@ }); |
@@ -17,6 +17,18 @@ // @ts-check | ||
forEachLine(lineMetadata(), (line, i, inCode, onFence, inTable, inItem) => { | ||
if ((((onFence > 0) && !isBlankLine(lines[i - 1])) || | ||
((onFence < 0) && !isBlankLine(lines[i + 1]))) && | ||
(includeListItems || !inItem)) { | ||
addErrorContext(onError, i + 1, lines[i].trim()); | ||
const onTopFence = (onFence > 0); | ||
const onBottomFence = (onFence < 0); | ||
if ((includeListItems || !inItem) && | ||
((onTopFence && !isBlankLine(lines[i - 1])) || | ||
(onBottomFence && !isBlankLine(lines[i + 1])))) { | ||
addErrorContext( | ||
onError, | ||
i + 1, | ||
lines[i].trim(), | ||
null, | ||
null, | ||
null, | ||
{ | ||
"lineNumber": i + (onTopFence ? 1 : 2), | ||
"insertText": "\n" | ||
}); | ||
} | ||
@@ -23,0 +35,0 @@ }); |
@@ -8,2 +8,4 @@ // @ts-check | ||
const quotePrefixRe = /^[>\s]*/; | ||
module.exports = { | ||
@@ -18,7 +20,30 @@ "names": [ "MD032", "blanks-around-lists" ], | ||
if (!isBlankLine(lines[firstIndex - 1])) { | ||
addErrorContext(onError, firstIndex + 1, lines[firstIndex].trim()); | ||
const line = lines[firstIndex]; | ||
const quotePrefix = line.match(quotePrefixRe)[0].trimRight(); | ||
addErrorContext( | ||
onError, | ||
firstIndex + 1, | ||
line.trim(), | ||
null, | ||
null, | ||
null, | ||
{ | ||
"insertText": `${quotePrefix}\n` | ||
}); | ||
} | ||
const lastIndex = list.lastLineIndex - 1; | ||
if (!isBlankLine(lines[lastIndex + 1])) { | ||
addErrorContext(onError, lastIndex + 1, lines[lastIndex].trim()); | ||
const line = lines[lastIndex]; | ||
const quotePrefix = line.match(quotePrefixRe)[0].trimRight(); | ||
addErrorContext( | ||
onError, | ||
lastIndex + 1, | ||
line.trim(), | ||
null, | ||
null, | ||
null, | ||
{ | ||
"lineNumber": lastIndex + 2, | ||
"insertText": `${quotePrefix}\n` | ||
}); | ||
} | ||
@@ -25,0 +50,0 @@ }); |
@@ -21,11 +21,25 @@ // @ts-check | ||
inLink = false; | ||
} else if ((type === "text") && !inLink && | ||
(match = bareUrlRe.exec(content))) { | ||
const [ bareUrl ] = match; | ||
const index = line.indexOf(content); | ||
const range = (index === -1) ? null : [ | ||
line.indexOf(content) + match.index + 1, | ||
bareUrl.length | ||
]; | ||
addErrorContext(onError, lineNumber, bareUrl, null, null, range); | ||
} else if ((type === "text") && !inLink) { | ||
while ((match = bareUrlRe.exec(content)) !== null) { | ||
const [ bareUrl ] = match; | ||
const index = line.indexOf(content); | ||
const range = (index === -1) ? null : [ | ||
line.indexOf(content) + match.index + 1, | ||
bareUrl.length | ||
]; | ||
const fixInfo = range ? { | ||
"editColumn": range[0], | ||
"deleteCount": range[1], | ||
"insertText": `<${bareUrl}>` | ||
} : null; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
bareUrl, | ||
null, | ||
null, | ||
range, | ||
fixInfo | ||
); | ||
} | ||
} | ||
@@ -32,0 +46,0 @@ }); |
@@ -7,2 +7,5 @@ // @ts-check | ||
const leftSpaceRe = /(?:^|\s)(\*\*?\*?|__?_?)\s.*[^\\]\1/g; | ||
const rightSpaceRe = /(?:^|[^\\])(\*\*?\*?|__?_?).+\s\1(?:\s|$)/g; | ||
module.exports = { | ||
@@ -14,21 +17,38 @@ "names": [ "MD037", "no-space-in-emphasis" ], | ||
forEachInlineChild(params, "text", (token) => { | ||
let left = true; | ||
let match = /(?:^|\s)(\*\*?|__?)\s.*[^\\]\1/.exec(token.content); | ||
if (!match) { | ||
left = false; | ||
match = /(?:^|[^\\])(\*\*?|__?).+\s\1(?:\s|$)/.exec(token.content); | ||
} | ||
if (match) { | ||
const fullText = match[0]; | ||
const line = params.lines[token.lineNumber - 1]; | ||
if (line.includes(fullText)) { | ||
const text = fullText.trim(); | ||
const column = line.indexOf(text) + 1; | ||
const length = text.length; | ||
addErrorContext(onError, token.lineNumber, | ||
text, left, !left, [ column, length ]); | ||
const { content, lineNumber } = token; | ||
const columnsReported = []; | ||
[ leftSpaceRe, rightSpaceRe ].forEach((spaceRe, index) => { | ||
let match = null; | ||
while ((match = spaceRe.exec(content)) !== null) { | ||
const [ fullText, marker ] = match; | ||
const line = params.lines[lineNumber - 1]; | ||
if (line.includes(fullText)) { | ||
const text = fullText.trim(); | ||
const column = line.indexOf(text) + 1; | ||
if (!columnsReported.includes(column)) { | ||
const length = text.length; | ||
const markerLength = marker.length; | ||
const emphasized = | ||
text.slice(markerLength, length - markerLength); | ||
const fixedText = `${marker}${emphasized.trim()}${marker}`; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
text, | ||
index === 0, | ||
index !== 0, | ||
[ column, length ], | ||
{ | ||
"editColumn": column, | ||
"deleteCount": length, | ||
"insertText": fixedText | ||
} | ||
); | ||
columnsReported.push(column); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
}; |
@@ -8,4 +8,4 @@ // @ts-check | ||
const startRe = /^\s([^`]|$)/; | ||
const endRe = /[^`]\s$/; | ||
const leftSpaceRe = /^\s([^`]|$)/; | ||
const rightSpaceRe = /[^`]\s$/; | ||
@@ -26,18 +26,38 @@ module.exports = { | ||
let rangeLineOffset = 0; | ||
let fixIndex = columnIndex; | ||
let fixLength = code.length; | ||
const codeLines = code.split(newLineRe); | ||
const left = startRe.test(code); | ||
const right = !left && endRe.test(code); | ||
const left = leftSpaceRe.test(code); | ||
const right = !left && rightSpaceRe.test(code); | ||
if (right && (codeLines.length > 1)) { | ||
rangeIndex = 0; | ||
rangeLineOffset = codeLines.length - 1; | ||
fixIndex = 0; | ||
} | ||
if (left || right) { | ||
const codeLinesRange = codeLines[rangeLineOffset]; | ||
if (codeLines.length > 1) { | ||
rangeLength = codeLines[rangeLineOffset].length + tickCount; | ||
rangeLength = codeLinesRange.length + tickCount; | ||
fixLength = codeLinesRange.length; | ||
} | ||
const context = tokenLines[lineIndex + rangeLineOffset] | ||
.substring(rangeIndex, rangeIndex + rangeLength); | ||
const codeLinesRangeTrim = codeLinesRange.trim(); | ||
const fixText = | ||
(codeLinesRangeTrim.startsWith("`") ? " " : "") + | ||
codeLinesRangeTrim + | ||
(codeLinesRangeTrim.endsWith("`") ? " " : ""); | ||
addErrorContext( | ||
onError, token.lineNumber + lineIndex + rangeLineOffset, | ||
context, left, right, [ rangeIndex + 1, rangeLength ]); | ||
onError, | ||
token.lineNumber + lineIndex + rangeLineOffset, | ||
context, | ||
left, | ||
right, | ||
[ rangeIndex + 1, rangeLength ], | ||
{ | ||
"editColumn": fixIndex + 1, | ||
"deleteCount": fixLength, | ||
"insertText": fixText | ||
} | ||
); | ||
} | ||
@@ -44,0 +64,0 @@ }); |
@@ -5,4 +5,3 @@ // @ts-check | ||
const { addErrorContext, filterTokens, rangeFromRegExp } = | ||
require("../helpers"); | ||
const { addErrorContext, filterTokens } = require("../helpers"); | ||
@@ -16,10 +15,14 @@ const spaceInLinkRe = /\[(?:\s+(?:[^\]]*?)\s*|(?:[^\]]*?)\s+)](?=\(\S*\))/; | ||
"function": function MD039(params, onError) { | ||
filterTokens(params, "inline", function forToken(token) { | ||
filterTokens(params, "inline", (token) => { | ||
const { children } = token; | ||
let { lineNumber } = token; | ||
let inLink = false; | ||
let linkText = ""; | ||
token.children.forEach(function forChild(child) { | ||
if (child.type === "link_open") { | ||
let lineIndex = 0; | ||
children.forEach((child) => { | ||
const { content, type } = child; | ||
if (type === "link_open") { | ||
inLink = true; | ||
linkText = ""; | ||
} else if (child.type === "link_close") { | ||
} else if (type === "link_close") { | ||
inLink = false; | ||
@@ -29,8 +32,26 @@ const left = linkText.trimLeft().length !== linkText.length; | ||
if (left || right) { | ||
addErrorContext(onError, token.lineNumber, | ||
"[" + linkText + "]", left, right, | ||
rangeFromRegExp(token.line, spaceInLinkRe)); | ||
const line = params.lines[lineNumber - 1]; | ||
const match = line.slice(lineIndex).match(spaceInLinkRe); | ||
const column = match.index + lineIndex + 1; | ||
const length = match[0].length; | ||
lineIndex = column + length - 1; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
`[${linkText}]`, | ||
left, | ||
right, | ||
[ column, length ], | ||
{ | ||
"editColumn": column + 1, | ||
"deleteCount": length - 2, | ||
"insertText": linkText.trim() | ||
} | ||
); | ||
} | ||
} else if ((type === "softbreak") || (type === "hardbreak")) { | ||
lineNumber++; | ||
lineIndex = 0; | ||
} else if (inLink) { | ||
linkText += child.content; | ||
linkText += content; | ||
} | ||
@@ -37,0 +58,0 @@ }); |
@@ -32,5 +32,26 @@ // @ts-check | ||
const lineNumber = token.lineNumber + index + fenceOffset; | ||
const range = [ match.index + 1, wordMatch.length ]; | ||
addErrorDetailIf(onError, lineNumber, | ||
name, match[1], null, null, range); | ||
const fullLine = params.lines[lineNumber - 1]; | ||
let matchIndex = match.index; | ||
const matchLength = wordMatch.length; | ||
const fullLineWord = | ||
fullLine.slice(matchIndex, matchIndex + matchLength); | ||
if (fullLineWord !== wordMatch) { | ||
// Attempt to fix bad offset due to inline content | ||
matchIndex = fullLine.indexOf(wordMatch); | ||
} | ||
const range = [ matchIndex + 1, matchLength ]; | ||
addErrorDetailIf( | ||
onError, | ||
lineNumber, | ||
name, | ||
match[1], | ||
null, | ||
null, | ||
range, | ||
{ | ||
"editColumn": matchIndex + 1, | ||
"deleteCount": matchLength, | ||
"insertText": name | ||
} | ||
); | ||
} | ||
@@ -37,0 +58,0 @@ } |
@@ -15,5 +15,15 @@ // @ts-check | ||
if (!isBlankLine(lastLine)) { | ||
addError(onError, lastLineNumber); | ||
addError( | ||
onError, | ||
lastLineNumber, | ||
null, | ||
null, | ||
[ lastLine.length, 1 ], | ||
{ | ||
"insertText": "\n", | ||
"editColumn": lastLine.length + 1 | ||
} | ||
); | ||
} | ||
} | ||
}; |
{ | ||
"name": "markdownlint", | ||
"version": "0.16.0", | ||
"version": "0.17.0", | ||
"description": "A Node.js style checker and lint tool for Markdown/CommonMark files.", | ||
@@ -30,9 +30,9 @@ "main": "lib/markdownlint.js", | ||
"dependencies": { | ||
"markdown-it": "9.0.1" | ||
"markdown-it": "10.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "~12.6.9", | ||
"browserify": "~16.3.0", | ||
"@types/node": "~12.7.11", | ||
"browserify": "~16.5.0", | ||
"cpy-cli": "~2.0.0", | ||
"eslint": "~6.1.0", | ||
"eslint": "~6.5.1", | ||
"glob": "~7.1.4", | ||
@@ -44,9 +44,9 @@ "js-yaml": "~3.13.1", | ||
"markdown-it-sup": "~1.0.0", | ||
"markdownlint-rule-helpers": "~0.3.0", | ||
"markdownlint-rule-helpers": "~0.4.0", | ||
"nodeunit": "~0.11.3", | ||
"nyc": "~14.1.1", | ||
"rimraf": "~2.6.3", | ||
"rimraf": "~3.0.0", | ||
"toml": "~3.0.0", | ||
"tv4": "~1.3.0", | ||
"typescript": "~3.5.3", | ||
"typescript": "~3.6.3", | ||
"uglify-js": "~3.6.0" | ||
@@ -53,0 +53,0 @@ }, |
@@ -419,12 +419,17 @@ # markdownlint | ||
Passing a `resultVersion` of `0` corresponds to the original, simple format where | ||
each error is identified by rule name and line number. This is deprecated. | ||
each error is identified by rule name and line number. *This is deprecated.* | ||
Passing a `resultVersion` of `1` corresponds to a detailed format where each error | ||
includes information about the line number, rule name, alias, description, as well | ||
as any additional detail or context that is available. This is deprecated. | ||
as any additional detail or context that is available. *This is deprecated.* | ||
Passing a `resultVersion` of `2` corresponds to a detailed format where each error | ||
includes information about the line number, rule names, description, as well as any | ||
additional detail or context that is available. This is the default. | ||
additional detail or context that is available. *This is the default.* | ||
Passing a `resultVersion` of `3` corresponds to the detailed version `2` format | ||
with additional information about how to fix automatically-fixable errors. In this | ||
mode, all errors that occur on each line are reported (other versions report only | ||
the first error for each rule). | ||
##### options.markdownItPlugins | ||
@@ -734,2 +739,3 @@ | ||
* [Boostnote](https://boostnote.io/) ([Search repository](https://github.com/BoostIO/Boostnote/search?q=markdownlint)) | ||
* [CodiMD](https://github.com/hackmdio/codimd) ([Search repository](https://github.com/hackmdio/codimd/search?q=markdownlint)) | ||
* [ESLint](https://eslint.org/) ([Search repository](https://github.com/eslint/eslint/search?q=markdownlint)) | ||
@@ -801,2 +807,5 @@ * [Garden React Components](https://garden.zendesk.com/react-components/) ([Search repository](https://github.com/zendeskgarden/react-components/search?q=markdownlint)) | ||
update dependencies. | ||
* 0.17.0 - Add `resultVersion` 3 to support fix information for default and custom rules, | ||
add fix information for 24 rules, update newline handling to match latest | ||
CommonMark specification, improve MD014/MD037/MD039, update dependencies. | ||
@@ -803,0 +812,0 @@ [npm-image]: https://img.shields.io/npm/v/markdownlint.svg |
@@ -0,0 +0,0 @@ { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
424669
62
9405
815
6
+ Addedentities@2.0.3(transitive)
+ Addedmarkdown-it@10.0.0(transitive)
- Removedentities@1.1.2(transitive)
- Removedmarkdown-it@9.0.1(transitive)
Updatedmarkdown-it@10.0.0