markdownlint-rule-search-replace
Advanced tools
Comparing version 0.1.0 to 1.0.0
@@ -102,16 +102,18 @@ { | ||
"MD050": false, | ||
"search-replace": [ | ||
{ | ||
"name": "ellipsis", | ||
"message": "Do not use three dots '...' for ellipsis.", | ||
"search": "...", | ||
"replace": "…" | ||
}, | ||
{ | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"search_pattern": "/“|”/g", | ||
"replace": "\"" | ||
} | ||
], | ||
"search-replace": { | ||
"rules": [ | ||
{ | ||
"name": "ellipsis", | ||
"message": "Do not use three dots '...' for ellipsis.", | ||
"search": "...", | ||
"replace": "…" | ||
}, | ||
{ | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"search_pattern": "/“|”/g", | ||
"replace": "\"" | ||
} | ||
] | ||
} | ||
} |
{ | ||
"name": "markdownlint-rule-search-replace", | ||
"version": "0.1.0", | ||
"version": "1.0.0", | ||
"description": "A custom markdownlint rule for search and replaces", | ||
@@ -5,0 +5,0 @@ "main": "rule.js", |
@@ -16,2 +16,3 @@ # markdownlint-rule-search-replace | ||
Or specific cases like replace three backticks with one. [[ref](https://github.com/DavidAnson/markdownlint/issues/411)] | ||
Or ban certain words. | ||
@@ -32,3 +33,2 @@ | ||
### Using .markdownlint.json config file | ||
@@ -39,16 +39,23 @@ | ||
```json | ||
"search-replace": [ | ||
{ | ||
"name": "ellipsis", | ||
"message": "Do not use three dots '...' for ellipsis.", | ||
"search": "...", | ||
"replace": "…" | ||
}, | ||
{ | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"search_pattern": "/“|”/g", | ||
"replace": "\"" | ||
{ | ||
"default": true, | ||
"MD001": false, | ||
"search-replace": { | ||
"rules": [ | ||
{ | ||
"name": "ellipsis", | ||
"message": "Do not use three dots '...' for ellipsis.", | ||
"search": "...", | ||
"replace": "…", | ||
"skip_code": true | ||
}, | ||
{ | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"search_pattern": "/“|”/g", | ||
"replace": "\"" | ||
} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
@@ -60,5 +67,6 @@ Here, | ||
- `message`: corresponding message | ||
- `search`: plain text to search | ||
- `search`: text to search | ||
- `search_pattern`: regex pattern to search. Include flags as well, as if you are defining a regex literal in JavaScript, e.g. `/http/g`. | ||
- `replace`: The replacement text, e.g. `https`. Regex properties like `$1` can be used if `search_pattern` is being used. | ||
- `replace`: The replacement string, e.g. `https`. Regex properties like `$1` can be used if `search_pattern` is being used. | ||
- `skip_code`: Optional. All code(inline and block), which is inside backticks, will be skipped. | ||
@@ -96,3 +104,3 @@ Note, `search` and `search_pattern` are interchangeable. The property `search` is used if both are supplied. | ||
# or | ||
markdonwlint test.md -r markdownlint-rule-search-replace --fix | ||
markdownlint test.md -r markdownlint-rule-search-replace --fix | ||
``` | ||
@@ -104,3 +112,3 @@ | ||
``` | ||
```js | ||
const searchReplace = require("markdownlint-rule-search-replace"); | ||
@@ -107,0 +115,0 @@ |
141
rule.js
@@ -5,2 +5,3 @@ // @ts-check | ||
module.exports = { | ||
@@ -13,38 +14,51 @@ names: ["search-replace"], | ||
function: (params, onError) => { | ||
if(!params.config.rules) { | ||
return; | ||
} | ||
const content = params.lines.join("\n"); | ||
const [backticksData, htmlCommentData] = gatherInfo(content); | ||
if (Array.isArray(params.config)) { | ||
params.config.forEach((rule) => { | ||
const regex = stringToRegex(rule.search || rule.search_pattern); | ||
params.config.rules.forEach((rule) => { | ||
const regex = stringToRegex(rule.search || rule.search_pattern); | ||
let result; | ||
while ((result = regex.exec(content)) !== null) { | ||
const match = result[0]; | ||
const [lineNo, columnNo] = getLocation(params.lines, result.index); | ||
let result; | ||
while ((result = regex.exec(content)) !== null) { | ||
const match = result[0]; | ||
let replacement = ""; | ||
if (rule.search) { | ||
replacement = rule.replace; | ||
} else { | ||
replacement = match.replace(new RegExp(regex), rule.replace); | ||
} | ||
if(rule.skip_code && isCode(result.index, backticksData)) { | ||
continue; | ||
} | ||
onError({ | ||
if(isHTMLComment(result.index, htmlCommentData)) { | ||
continue; | ||
} | ||
const [lineNo, columnNo] = getLocation(params.lines, result.index); | ||
let replacement = ""; | ||
if (rule.search) { | ||
replacement = rule.replace; | ||
} else { | ||
replacement = match.replace(new RegExp(regex), rule.replace); | ||
} | ||
onError({ | ||
lineNumber: lineNo, | ||
detail: rule.name + ": " + rule.message, | ||
context: match, | ||
range: [columnNo + 1, match.length], | ||
fixInfo: { | ||
lineNumber: lineNo, | ||
detail: rule.name + ": " + rule.message, | ||
context: match, | ||
range: [columnNo + 1, match.length], | ||
fixInfo: { | ||
lineNumber: lineNo, | ||
editColumn: columnNo + 1, | ||
deleteCount: match.length, | ||
insertText: replacement, | ||
}, | ||
}); | ||
} | ||
}); | ||
} | ||
editColumn: columnNo + 1, | ||
deleteCount: match.length, | ||
insertText: replacement, | ||
}, | ||
}); | ||
} | ||
}); | ||
}, | ||
}; | ||
const escapeForRegExp = (str) => { | ||
@@ -54,2 +68,8 @@ return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); | ||
/** | ||
* Converts string "/abc/ig" to new RegEx("abc", "ig"). | ||
* Or | ||
* Converts string "abc" to new RegEx("abc", "g"). | ||
*/ | ||
const stringToRegex = (str) => { | ||
@@ -66,2 +86,6 @@ let pattern = (str.match(/\/(.+)\/.*/) || [])[1]; | ||
/** | ||
* Given position in the document returns line and column number. | ||
*/ | ||
const getLocation = (lines, pos) => { | ||
@@ -79,1 +103,64 @@ let lineNo = 1; | ||
}; | ||
/** | ||
* Collect start and end position data of all code fences and html comments. | ||
*/ | ||
const gatherInfo = (content) => { | ||
const backtickRgx = /`+/g; | ||
const backticksData = []; | ||
let match = null; | ||
let lastLength = null; | ||
while ((match = backtickRgx.exec(content)) !== null) { | ||
const length = match[0].length; | ||
const pos = match.index; | ||
if(!lastLength) { | ||
backticksData.push([length, pos]); | ||
lastLength = length; | ||
} else if(lastLength === length) { | ||
backticksData.push([length, pos]); | ||
lastLength = null; | ||
} | ||
} | ||
const htmlCommentRgx = /<!--|-->/g; | ||
const htmlCommentData = []; | ||
while ((match = htmlCommentRgx.exec(content)) !== null) { | ||
htmlCommentData.push([match[0].length, match.index]); | ||
} | ||
return [backticksData, htmlCommentData]; | ||
}; | ||
/** | ||
* Tells if the position lies inside a code block. | ||
*/ | ||
const isCode = (pos, data) => { | ||
return isPartOf(pos, data); | ||
}; | ||
/** | ||
* Tells if the position lies inside an HTML comment. | ||
*/ | ||
const isHTMLComment = (pos, data) => { | ||
return isPartOf(pos, data); | ||
}; | ||
/** | ||
* Tells if the position lies inside any block in the 'data' array. | ||
*/ | ||
const isPartOf = (pos, data) => { | ||
for(let i = 0; i < data.length; i+=2 ) { | ||
const start = data[i][1]; | ||
const end = data[i+1][0] + data[i+1][1]; | ||
if(start <= pos && pos <= end ) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; |
@@ -24,1 +24,3 @@ --- | ||
Some _112_ text. | ||
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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
12200
246
1
121