markdownlint
Advanced tools
Comparing version 0.19.0 to 0.20.0
@@ -477,3 +477,3 @@ # Rules | ||
Parameters: line_length, heading_line_length, code_block_line_length, code_blocks, tables, headings, headers, strict (number; default 80, boolean; default true) | ||
Parameters: line_length, heading_line_length, code_block_line_length, code_blocks, tables, headings, headers, strict, stern (number; default 80 for *_length, boolean; default true (except strict/stern which default false)) | ||
@@ -488,7 +488,21 @@ > If `headings` is not provided, `headers` (deprecated) will be used. | ||
This rule has an exception where there is no whitespace beyond the configured | ||
This rule has an exception when there is no whitespace beyond the configured | ||
line length. This allows you to still include items such as long URLs without | ||
being forced to break them in the middle. To disable this exception, set the | ||
`strict` parameter to `true`. | ||
`strict` parameter to `true` to report an issue when any line is too long. | ||
To warn for lines that are too long and could be fixed but allow lines without | ||
spaces, set the `stern` parameter to `true`. | ||
For example (assuming normal behavior): | ||
```markdown | ||
IF THIS LINE IS THE MAXIMUM LENGTH | ||
This line is okay because there are-no-spaces-beyond-that-length | ||
And this line is a violation because there are | ||
This-line-is-also-okay-because-there-are-no-spaces | ||
``` | ||
In `strict` or `stern` modes, the two middle lines above are a violation. The | ||
third line is a violation in `strict` mode, but allowed in `stern` mode. | ||
You have the option to exclude this rule for code blocks, tables, or headings. | ||
@@ -950,4 +964,4 @@ To do so, set the `code_blocks`, `tables`, or `headings` parameter(s) to false. | ||
do not have a prefix that increases in numerical order (depending on the | ||
configured style). The less-common pattern of using '0.' for all prefixes is | ||
also supported. | ||
configured style). The less-common patterns of using '0.' as a first prefix or | ||
for all prefixes is also supported. | ||
@@ -962,3 +976,3 @@ Example valid list if the style is configured as 'one': | ||
Example valid list if the style is configured as 'ordered': | ||
Examples of valid lists if the style is configured as 'ordered': | ||
@@ -971,4 +985,10 @@ ```markdown | ||
Both examples are valid when the style is configured as 'one_or_ordered'. | ||
```markdown | ||
0. Do this. | ||
1. Do that. | ||
2. Done. | ||
``` | ||
All three examples are valid when the style is configured as 'one_or_ordered'. | ||
Example valid list if the style is configured as 'zero': | ||
@@ -1000,2 +1020,26 @@ | ||
Note: This rule will report violations for cases like the following where an improperly-indented code block (or similar) appears between two list items and "breaks" the list in two: | ||
~~~markdown | ||
1. First list | ||
```text | ||
Code block | ||
``` | ||
1. Second list | ||
~~~ | ||
The fix is to indent the code block so it becomes part of the preceding list item as intended: | ||
~~~markdown | ||
1. First list | ||
```text | ||
Code block | ||
``` | ||
2. Still first list | ||
~~~ | ||
Rationale: Consistent formatting makes it easier to understand a document. | ||
@@ -1213,5 +1257,4 @@ | ||
Note: if you do want a bare URL without it being converted into a link, | ||
enclose it in a code block, otherwise in some markdown parsers it _will_ be | ||
converted: | ||
Note: To use a bare URL without it being converted into a link, enclose it in | ||
a code block, otherwise in some markdown parsers it _will_ be converted: | ||
@@ -1222,3 +1265,17 @@ ```markdown | ||
Rationale: Without angle brackets, the URL isn't converted into a link in many | ||
Note: The following scenario does _not_ trigger this rule to avoid conflicts | ||
with `MD011`/`no-reversed-links`: | ||
```markdown | ||
[https://www.example.com] | ||
``` | ||
The use of quotes around a bare link will _not_ trigger this rule, either: | ||
```markdown | ||
"https://www.example.com" | ||
'https://www.example.com' | ||
``` | ||
Rationale: Without angle brackets, the URL isn't converted into a link by many | ||
markdown parsers. | ||
@@ -1362,8 +1419,6 @@ | ||
This rule is triggered on code span elements that have spaces right inside the | ||
This rule is triggered for code span elements that have spaces adjacent to the | ||
backticks: | ||
```markdown | ||
` some text ` | ||
`some text ` | ||
@@ -1374,3 +1429,3 @@ | ||
To fix this, remove the spaces inside the codespan markers: | ||
To fix this, remove any spaces adjacent to the backticks: | ||
@@ -1381,2 +1436,9 @@ ```markdown | ||
Note: A single leading and trailing space is allowed by the specification and | ||
automatically trimmed (to allow for embedded backticks): | ||
```markdown | ||
`` `backticks` `` | ||
``` | ||
Note: A single leading or trailing space is allowed if used to separate codespan | ||
@@ -1383,0 +1445,0 @@ markers from an embedded backtick: |
@@ -20,7 +20,7 @@ // @ts-check | ||
// eslint-disable-next-line max-len | ||
/<!--\s*markdownlint-(disable|enable|capture|restore|disable-file|enable-file)((?:\s+[a-z0-9_-]+)*)\s*-->/ig; | ||
/<!--\s*markdownlint-(?:(?:(disable|enable|capture|restore|disable-file|enable-file)((?:\s+[a-z0-9_-]+)*))|(?:(configure-file)\s+([\s\S]*?)))\s*-->/ig; | ||
module.exports.inlineCommentRe = inlineCommentRe; | ||
// Regular expressions for range matching | ||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s]*/ig; | ||
module.exports.bareUrlRe = /(?:http|ftp)s?:\/\/[^\s\]"']*/ig; | ||
module.exports.listItemMarkerRe = /^([\s>]*)(?:[*+-]|\d+[.)])\s+/; | ||
@@ -100,6 +100,6 @@ module.exports.orderedListItemMarkerRe = /^[\s>]*0*(\d+)[.)]/; | ||
while ((i = text.indexOf(htmlCommentBegin, i)) !== -1) { | ||
let j = text.indexOf(htmlCommentEnd, i); | ||
const j = text.indexOf(htmlCommentEnd, i); | ||
if (j === -1) { | ||
j = text.length; | ||
text += "\\\n"; | ||
// Un-terminated comments are treated as text | ||
break; | ||
} | ||
@@ -239,6 +239,11 @@ const comment = text.slice(i + htmlCommentBegin.length, j); | ||
filterTokens(params, "list_item_open", function forToken(token) { | ||
let count = 1; | ||
for (let i = token.map[0]; i < token.map[1]; i++) { | ||
lineMetadata[i][5] = true; | ||
lineMetadata[i][5] = count; | ||
count++; | ||
} | ||
}); | ||
filterTokens(params, "hr", function forToken(token) { | ||
lineMetadata[token.map[0]][6] = true; | ||
}); | ||
return lineMetadata; | ||
@@ -250,3 +255,3 @@ }; | ||
lineMetadata.forEach(function forMetadata(metadata) { | ||
// Parameters: line, lineIndex, inCode, onFence, inTable | ||
// Parameters: line, lineIndex, inCode, onFence, inTable, inBreak | ||
handler(...metadata); | ||
@@ -261,2 +266,4 @@ }); | ||
let current = null; | ||
let nesting = 0; | ||
const nestingStack = []; | ||
let lastWithMap = { "map": [ 0, 1 ] }; | ||
@@ -276,6 +283,7 @@ params.tokens.forEach(function forToken(token) { | ||
"items": [], | ||
"nesting": stack.length - 1, | ||
"nesting": nesting, | ||
"lastLineIndex": -1, | ||
"insert": flattenedLists.length | ||
}; | ||
nesting++; | ||
} else if ((token.type === "bullet_list_close") || | ||
@@ -288,5 +296,11 @@ (token.type === "ordered_list_close")) { | ||
current = stack.pop(); | ||
nesting--; | ||
} else if (token.type === "list_item_open") { | ||
// Add list item | ||
current.items.push(token); | ||
} else if (token.type === "blockquote_open") { | ||
nestingStack.push(nesting); | ||
nesting = 0; | ||
} else if (token.type === "blockquote_close") { | ||
nesting = nestingStack.pop(); | ||
} else if (token.map) { | ||
@@ -293,0 +307,0 @@ // Track last token with map |
{ | ||
"name": "markdownlint-rule-helpers", | ||
"version": "0.7.0", | ||
"version": "0.8.0", | ||
"description": "A collection of markdownlint helper functions for custom rules", | ||
@@ -5,0 +5,0 @@ "main": "helpers.js", |
@@ -303,6 +303,6 @@ // @ts-check | ||
* @param {boolean} noInlineConfig Whether to allow inline configuration. | ||
* @param {Configuration} effectiveConfig Effective configuration. | ||
* @param {Configuration} config Configuration object. | ||
* @param {Object.<string, string[]>} aliasToRuleNames Map of alias to rule | ||
* names. | ||
* @returns {Object.<string, RuleConfiguration>[]} Enabled rules for each line. | ||
* @returns {Object} Effective configuration and enabled rules per line number. | ||
*/ | ||
@@ -314,58 +314,91 @@ function getEnabledRulesPerLineNumber( | ||
noInlineConfig, | ||
effectiveConfig, | ||
config, | ||
aliasToRuleNames) { | ||
// Shared variables | ||
let enabledRules = {}; | ||
let capturedRules = {}; | ||
const allRuleNames = []; | ||
ruleList.forEach((rule) => { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
allRuleNames.push(ruleName); | ||
enabledRules[ruleName] = !!effectiveConfig[ruleName]; | ||
}); | ||
let capturedRules = enabledRules; | ||
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); | ||
// Helper functions | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function forMatch(match, byLine) { | ||
const action = match[1].toUpperCase(); | ||
if (action === "CAPTURE") { | ||
if (byLine) { | ||
capturedRules = { ...enabledRules }; | ||
function handleInlineConfig(perLine, forEachMatch, forEachLine) { | ||
const input = perLine ? lines : [ lines.join("\n") ]; | ||
input.forEach((line) => { | ||
if (!noInlineConfig) { | ||
let match = null; | ||
while ((match = helpers.inlineCommentRe.exec(line))) { | ||
const action = (match[1] || match[3]).toUpperCase(); | ||
const parameter = match[2] || match[4]; | ||
forEachMatch(action, parameter); | ||
} | ||
} | ||
} else if (action === "RESTORE") { | ||
if (byLine) { | ||
enabledRules = { ...capturedRules }; | ||
if (forEachLine) { | ||
forEachLine(); | ||
} | ||
} else { | ||
// action in [ENABLE, DISABLE, ENABLE-FILE, DISABLE-FILE] | ||
const isfile = action.endsWith("-FILE"); | ||
if ((byLine && !isfile) || (!byLine && isfile)) { | ||
const enabled = (action.startsWith("ENABLE")); | ||
const items = match[2] ? | ||
match[2].trim().toUpperCase().split(/\s+/) : | ||
allRuleNames; | ||
items.forEach((nameUpper) => { | ||
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { | ||
enabledRules[ruleName] = enabled; | ||
}); | ||
}); | ||
}); | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function configureFile(action, parameter) { | ||
if (action === "CONFIGURE-FILE") { | ||
try { | ||
const json = JSON.parse(parameter); | ||
config = { | ||
...config, | ||
...json | ||
}; | ||
} catch (ex) { | ||
// Ignore parse errors for inline configuration | ||
} | ||
} | ||
} | ||
const enabledRulesPerLineNumber = new Array(1 + frontMatterLines.length); | ||
[ false, true ].forEach((byLine) => { | ||
lines.forEach((line) => { | ||
if (!noInlineConfig) { | ||
let match = helpers.inlineCommentRe.exec(line); | ||
if (match) { | ||
enabledRules = { ...enabledRules }; | ||
while (match) { | ||
forMatch(match, byLine); | ||
match = helpers.inlineCommentRe.exec(line); | ||
} | ||
} | ||
} | ||
if (byLine) { | ||
enabledRulesPerLineNumber.push(enabledRules); | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function applyEnableDisable(action, parameter) { | ||
const enabled = (action.startsWith("ENABLE")); | ||
const items = parameter ? | ||
parameter.trim().toUpperCase().split(/\s+/) : | ||
allRuleNames; | ||
items.forEach((nameUpper) => { | ||
(aliasToRuleNames[nameUpper] || []).forEach((ruleName) => { | ||
enabledRules[ruleName] = enabled; | ||
}); | ||
}); | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function enableDisableFile(action, parameter) { | ||
if ((action === "ENABLE-FILE") || (action === "DISABLE-FILE")) { | ||
applyEnableDisable(action, parameter); | ||
} | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function captureRestoreEnableDisable(action, parameter) { | ||
if (action === "CAPTURE") { | ||
capturedRules = { ...enabledRules }; | ||
} else if (action === "RESTORE") { | ||
enabledRules = { ...capturedRules }; | ||
} else if ((action === "ENABLE") || (action === "DISABLE")) { | ||
enabledRules = { ...enabledRules }; | ||
applyEnableDisable(action, parameter); | ||
} | ||
} | ||
// eslint-disable-next-line jsdoc/require-jsdoc | ||
function updateLineState() { | ||
enabledRulesPerLineNumber.push(enabledRules); | ||
} | ||
// Handle inline comments | ||
handleInlineConfig(false, configureFile); | ||
const effectiveConfig = getEffectiveConfig( | ||
ruleList, config, aliasToRuleNames); | ||
ruleList.forEach((rule) => { | ||
const ruleName = rule.names[0].toUpperCase(); | ||
allRuleNames.push(ruleName); | ||
enabledRules[ruleName] = !!effectiveConfig[ruleName]; | ||
}); | ||
return enabledRulesPerLineNumber; | ||
capturedRules = enabledRules; | ||
handleInlineConfig(true, enableDisableFile); | ||
handleInlineConfig(true, captureRestoreEnableDisable, updateLineState); | ||
// Return results | ||
return { | ||
effectiveConfig, | ||
enabledRulesPerLineNumber | ||
}; | ||
} | ||
@@ -443,7 +476,11 @@ | ||
const aliasToRuleNames = mapAliasToRuleNames(ruleList); | ||
const effectiveConfig = | ||
getEffectiveConfig(ruleList, config, aliasToRuleNames); | ||
const enabledRulesPerLineNumber = getEnabledRulesPerLineNumber( | ||
ruleList, lines, frontMatterLines, noInlineConfig, | ||
effectiveConfig, aliasToRuleNames); | ||
const { effectiveConfig, enabledRulesPerLineNumber } = | ||
getEnabledRulesPerLineNumber( | ||
ruleList, | ||
lines, | ||
frontMatterLines, | ||
noInlineConfig, | ||
config, | ||
aliasToRuleNames | ||
); | ||
// Create parameters for rules | ||
@@ -450,0 +487,0 @@ const params = { |
@@ -22,2 +22,3 @@ // @ts-check | ||
const actualIndent = indentFor(item); | ||
let match = null; | ||
if (list.unordered) { | ||
@@ -34,4 +35,3 @@ addErrorDetailIf( | ||
); | ||
} else { | ||
const match = orderedListItemMarkerRe.exec(line); | ||
} else if ((match = orderedListItemMarkerRe.exec(line))) { | ||
actualEnd = match[0].length; | ||
@@ -38,0 +38,0 @@ expectedEnd = expectedEnd || actualEnd; |
@@ -14,2 +14,3 @@ // @ts-check | ||
const linkOrImageOnlyLineRe = /^[es]*(lT?L|I)[ES]*$/; | ||
const sternModeRe = /^([#>\s]*\s)?\S*$/; | ||
const tokenTypeMap = { | ||
@@ -37,4 +38,5 @@ "em_open": "e", | ||
const strict = !!params.config.strict; | ||
const stern = !!params.config.stern; | ||
const longLineRePostfix = | ||
strict ? longLineRePostfixStrict : longLineRePostfixRelaxed; | ||
(strict || stern) ? longLineRePostfixStrict : longLineRePostfixRelaxed; | ||
const longLineRe = | ||
@@ -84,3 +86,4 @@ new RegExp(longLineRePrefix + lineLength + longLineRePostfix); | ||
(strict || | ||
(!includesSorted(linkOnlyLineNumbers, lineNumber) && | ||
(!(stern && sternModeRe.test(line)) && | ||
!includesSorted(linkOnlyLineNumbers, lineNumber) && | ||
!labelRe.test(line))) && | ||
@@ -87,0 +90,0 @@ lengthRe.test(line)) { |
@@ -14,3 +14,6 @@ // @ts-check | ||
forEachLine(lineMetadata(), (line, lineIndex, inCode) => { | ||
if (!inCode && /^#+[^#\s]/.test(line) && !/#\s*$/.test(line)) { | ||
if (!inCode && | ||
/^#+[^#\s]/.test(line) && | ||
!/#\s*$/.test(line) && | ||
!line.startsWith("#️⃣")) { | ||
const hashCount = /^#+/.exec(line)[0].length; | ||
@@ -17,0 +20,0 @@ addErrorContext( |
@@ -21,24 +21,47 @@ // @ts-check | ||
const style = String(params.config.style || "one_or_ordered"); | ||
flattenedLists().forEach((list) => { | ||
if (!list.unordered) { | ||
let listStyle = style; | ||
if (listStyle === "one_or_ordered") { | ||
const second = (list.items.length > 1) && | ||
orderedListItemMarkerRe.exec(list.items[1].line); | ||
listStyle = (second && (second[1] !== "1")) ? "ordered" : "one"; | ||
flattenedLists().filter((list) => !list.unordered).forEach((list) => { | ||
const { items } = list; | ||
let current = 1; | ||
let incrementing = false; | ||
// Check for incrementing number pattern 1/2/3 or 0/1/2 | ||
if (items.length >= 2) { | ||
const first = orderedListItemMarkerRe.exec(items[0].line); | ||
const second = orderedListItemMarkerRe.exec(items[1].line); | ||
if (first && second) { | ||
const [ , firstNumber ] = first; | ||
const [ , secondNumber ] = second; | ||
if ((secondNumber !== "1") || (firstNumber === "0")) { | ||
incrementing = true; | ||
if (firstNumber === "0") { | ||
current = 0; | ||
} | ||
} | ||
} | ||
let number = (listStyle === "zero") ? 0 : 1; | ||
list.items.forEach((item) => { | ||
const match = orderedListItemMarkerRe.exec(item.line); | ||
} | ||
// Determine effective style | ||
let listStyle = style; | ||
if (listStyle === "one_or_ordered") { | ||
listStyle = incrementing ? "ordered" : "one"; | ||
} | ||
// Force expected value for 0/0/0 and 1/1/1 patterns | ||
if (listStyle === "zero") { | ||
current = 0; | ||
} else if (listStyle === "one") { | ||
current = 1; | ||
} | ||
// Validate each list item marker | ||
items.forEach((item) => { | ||
const match = orderedListItemMarkerRe.exec(item.line); | ||
if (match) { | ||
addErrorDetailIf(onError, item.lineNumber, | ||
String(number), !match || match[1], | ||
String(current), match[1], | ||
"Style: " + listStyleExamples[listStyle], null, | ||
rangeFromRegExp(item.line, listItemMarkerRe)); | ||
if (listStyle === "ordered") { | ||
number++; | ||
current++; | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
}; |
@@ -8,2 +8,4 @@ // @ts-check | ||
const codeFencePrefixRe = /^(.*?)\s*[`~]/; | ||
module.exports = { | ||
@@ -23,2 +25,3 @@ "names": [ "MD031", "blanks-around-fences" ], | ||
(onBottomFence && !isBlankLine(lines[i + 1])))) { | ||
const [ , prefix ] = line.match(codeFencePrefixRe); | ||
addErrorContext( | ||
@@ -33,3 +36,3 @@ onError, | ||
"lineNumber": i + (onTopFence ? 1 : 2), | ||
"insertText": "\n" | ||
"insertText": `${prefix}\n` | ||
}); | ||
@@ -36,0 +39,0 @@ } |
@@ -24,21 +24,34 @@ // @ts-check | ||
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 | ||
); | ||
const matchIndex = match.index; | ||
const bareUrlLength = bareUrl.length; | ||
// Allow "[https://example.com]" to avoid conflicts with | ||
// MD011/no-reversed-links; allow quoting as another way | ||
// of deliberately including a bare URL | ||
const leftChar = content[matchIndex - 1]; | ||
const rightChar = content[matchIndex + bareUrlLength]; | ||
if ( | ||
!((leftChar === "[") && (rightChar === "]")) && | ||
!((leftChar === "\"") && (rightChar === "\"")) && | ||
!((leftChar === "'") && (rightChar === "'")) | ||
) { | ||
const index = line.indexOf(content); | ||
const range = (index === -1) ? null : [ | ||
index + matchIndex + 1, | ||
bareUrlLength | ||
]; | ||
const fixInfo = range ? { | ||
"editColumn": range[0], | ||
"deleteCount": range[1], | ||
"insertText": `<${bareUrl}>` | ||
} : null; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
bareUrl, | ||
null, | ||
null, | ||
range, | ||
fixInfo | ||
); | ||
} | ||
} | ||
@@ -45,0 +58,0 @@ } |
@@ -5,6 +5,9 @@ // @ts-check | ||
const { addErrorContext, forEachInlineChild } = require("../helpers"); | ||
const { addErrorContext, forEachLine } = require("../helpers"); | ||
const { lineMetadata } = require("./cache"); | ||
const leftSpaceRe = /(?:^|\s)(\*\*?\*?|__?_?)\s.*[^\\]\1/g; | ||
const rightSpaceRe = /(?:^|[^\\])(\*\*?\*?|__?_?).+\s\1(?:\s|$)/g; | ||
const emphasisRe = /(^|[^\\])(?:(\*\*?\*?)|(__?_?))/g; | ||
const asteriskListItemMarkerRe = /^(\s*)\*(\s+)/; | ||
const leftSpaceRe = /^\s+/; | ||
const rightSpaceRe = /\s+$/; | ||
@@ -16,25 +19,47 @@ module.exports = { | ||
"function": function MD037(params, onError) { | ||
forEachInlineChild(params, "text", (token) => { | ||
const { content, lineNumber } = token; | ||
const columnsReported = []; | ||
[ leftSpaceRe, rightSpaceRe ].forEach((spaceRe, index) => { | ||
forEachLine( | ||
lineMetadata(), | ||
(line, lineIndex, inCode, onFence, inTable, inItem, inBreak) => { | ||
if (inCode || inBreak) { | ||
// Emphasis has no meaning here | ||
return; | ||
} | ||
if (inItem === 1) { | ||
// Trim overlapping '*' list item marker | ||
line = line.replace(asteriskListItemMarkerRe, "$1 $2"); | ||
} | ||
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}`; | ||
let emphasisIndex = -1; | ||
let emphasisLength = 0; | ||
let effectiveEmphasisLength = 0; | ||
// Match all emphasis-looking runs in the line... | ||
while ((match = emphasisRe.exec(line))) { | ||
const matchIndex = match.index + match[1].length; | ||
const matchLength = match[0].length - match[1].length; | ||
if (emphasisIndex === -1) { | ||
// New run | ||
emphasisIndex = matchIndex + matchLength; | ||
emphasisLength = matchLength; | ||
effectiveEmphasisLength = matchLength; | ||
} else if (matchLength === effectiveEmphasisLength) { | ||
// Close current run | ||
const content = line.substring(emphasisIndex, matchIndex); | ||
const leftSpace = leftSpaceRe.test(content); | ||
const rightSpace = rightSpaceRe.test(content); | ||
if (leftSpace || rightSpace) { | ||
// Report the violation | ||
const contextStart = emphasisIndex - emphasisLength; | ||
const contextEnd = matchIndex + effectiveEmphasisLength; | ||
const context = line.substring(contextStart, contextEnd); | ||
const column = contextStart + 1; | ||
const length = contextEnd - contextStart; | ||
const leftMarker = line.substring(contextStart, emphasisIndex); | ||
const rightMarker = match[2] || match[3]; | ||
const fixedText = `${leftMarker}${content.trim()}${rightMarker}`; | ||
addErrorContext( | ||
onError, | ||
lineNumber, | ||
text, | ||
index === 0, | ||
index !== 0, | ||
lineIndex + 1, | ||
context, | ||
leftSpace, | ||
rightSpace, | ||
[ column, length ], | ||
@@ -47,9 +72,21 @@ { | ||
); | ||
columnsReported.push(column); | ||
} | ||
// Reset | ||
emphasisIndex = -1; | ||
emphasisLength = 0; | ||
effectiveEmphasisLength = 0; | ||
} else if (matchLength === 3) { | ||
// Swap internal run length (1->2 or 2->1) | ||
effectiveEmphasisLength = matchLength - effectiveEmphasisLength; | ||
} else if (effectiveEmphasisLength === 3) { | ||
// Downgrade internal run (3->1 or 3->2) | ||
effectiveEmphasisLength -= matchLength; | ||
} else { | ||
// Upgrade to internal run (1->3 or 2->3) | ||
effectiveEmphasisLength += matchLength; | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
); | ||
} | ||
}; |
@@ -10,2 +10,3 @@ // @ts-check | ||
const rightSpaceRe = /[^`]\s$/; | ||
const singleLeftRightSpaceRe = /^\s\S+\s$/; | ||
@@ -36,3 +37,4 @@ module.exports = { | ||
} | ||
if (left || right) { | ||
const allowed = singleLeftRightSpaceRe.test(code); | ||
if ((left || right) && !allowed) { | ||
const codeLinesRange = codeLines[rangeLineOffset]; | ||
@@ -39,0 +41,0 @@ if (codeLines.length > 1) { |
@@ -31,6 +31,16 @@ // @ts-check | ||
const line = params.lines[lineNumber - 1]; | ||
let range = null; | ||
let fixInfo = null; | ||
const match = line.slice(lineIndex).match(spaceInLinkRe); | ||
const column = match.index + lineIndex + 1; | ||
const length = match[0].length; | ||
lineIndex = column + length - 1; | ||
if (match) { | ||
const column = match.index + lineIndex + 1; | ||
const length = match[0].length; | ||
range = [ column, length ]; | ||
fixInfo = { | ||
"editColumn": column + 1, | ||
"deleteCount": length - 2, | ||
"insertText": linkText.trim() | ||
}; | ||
lineIndex = column + length - 1; | ||
} | ||
addErrorContext( | ||
@@ -42,8 +52,4 @@ onError, | ||
right, | ||
[ column, length ], | ||
{ | ||
"editColumn": column + 1, | ||
"deleteCount": length - 2, | ||
"insertText": linkText.trim() | ||
} | ||
range, | ||
fixInfo | ||
); | ||
@@ -50,0 +56,0 @@ } |
{ | ||
"name": "markdownlint", | ||
"version": "0.19.0", | ||
"version": "0.20.0", | ||
"description": "A Node.js style checker and lint tool for Markdown/CommonMark files.", | ||
@@ -36,8 +36,8 @@ "main": "lib/markdownlint.js", | ||
"devDependencies": { | ||
"@types/node": "~13.5.0", | ||
"browserify": "~16.5.0", | ||
"c8": "~7.0.1", | ||
"cpy-cli": "~3.0.0", | ||
"@types/node": "~13.11.1", | ||
"browserify": "~16.5.1", | ||
"c8": "~7.1.0", | ||
"cpy-cli": "~3.1.0", | ||
"eslint": "~6.8.0", | ||
"eslint-plugin-jsdoc": "~21.0.0", | ||
"eslint-plugin-jsdoc": "~22.1.0", | ||
"glob": "~7.1.6", | ||
@@ -49,9 +49,10 @@ "js-yaml": "~3.13.1", | ||
"markdown-it-sup": "~1.0.0", | ||
"markdownlint-rule-helpers": "~0.6.0", | ||
"rimraf": "~3.0.0", | ||
"tape": "~4.13.0", | ||
"markdownlint-rule-helpers": "~0.7.0", | ||
"rimraf": "~3.0.2", | ||
"tape": "~4.13.2", | ||
"tape-player": "~0.1.0", | ||
"toml": "~3.0.0", | ||
"tv4": "~1.3.0", | ||
"typescript": "~3.7.5", | ||
"uglify-js": "~3.7.6" | ||
"typescript": "~3.8.3", | ||
"uglify-js": "~3.8.1" | ||
}, | ||
@@ -58,0 +59,0 @@ "keywords": [ |
@@ -42,2 +42,3 @@ # markdownlint | ||
* [markdownlint/mdl gem for Ruby](https://rubygems.org/gems/mdl) | ||
* [Cake.Markdownlint addin for Cake build automation system](https://github.com/cake-contrib/Cake.Markdownlint) | ||
@@ -199,2 +200,29 @@ ## Demonstration | ||
In cases where it is desirable to change the configuration of one or more rules | ||
for a file, the following more advanced syntax is supported: | ||
* Confiure: `<!-- markdownlint-configure-file { options.config JSON } -->` | ||
For example: | ||
```markdown | ||
<!-- markdownlint-configure-file { "MD013": { "line_length": 70 } } --> | ||
``` | ||
or | ||
```markdown | ||
<!-- markdownlint-configure-file | ||
{ | ||
"hr-style": { | ||
"style": "---" | ||
}, | ||
"no-trailing-spaces": false | ||
} | ||
--> | ||
``` | ||
These changes apply to the entire file regardless of where the comment is | ||
located. Multiple such comments (if present) are applied top-to-bottom. | ||
## API | ||
@@ -833,2 +861,5 @@ | ||
comments, update dependencies. | ||
* 0.20.0 - Add `markdownlint-configure-file` inline comment, | ||
improve MD005/MD007/MD013/MD018/MD029/MD031/MD034/MD037/MD038/MD039, improve HTML | ||
comment handling, update dependencies. | ||
@@ -835,0 +866,0 @@ [npm-image]: https://img.shields.io/npm/v/markdownlint.svg |
@@ -184,2 +184,7 @@ // @ts-check | ||
"default": false | ||
}, | ||
"stern": { | ||
"description": "Stern length checking", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
@@ -186,0 +191,0 @@ }; |
@@ -436,2 +436,7 @@ { | ||
"default": false | ||
}, | ||
"stern": { | ||
"description": "Stern length checking", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
@@ -488,2 +493,7 @@ }, | ||
"default": false | ||
}, | ||
"stern": { | ||
"description": "Stern length checking", | ||
"type": "boolean", | ||
"default": false | ||
} | ||
@@ -490,0 +500,0 @@ }, |
Sorry, the diff of this file is too big to display
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
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
488131
10958
869
20