markdownlint-rule-search-replace
Advanced tools
Comparing version 1.0.1 to 1.0.2
{ | ||
"name": "markdownlint-rule-search-replace", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "A custom markdownlint rule for search and replaces", | ||
@@ -22,7 +22,24 @@ "main": "rule.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"test": "ava tests/*-tests.js", | ||
"lint": "eslint --max-warnings 0 --fix .", | ||
"pretty-check": "prettier --check .", | ||
"pretty": "prettier --write ." | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
}, | ||
"dependencies": { | ||
"markdownlint-rule-helpers": "^0.16.0" | ||
}, | ||
"devDependencies": { | ||
"ava": "^4.3.0", | ||
"eslint": "^8.19.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-es": "^4.1.0", | ||
"eslint-plugin-jsdoc": "^39.3.3", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-unicorn": "^43.0.1", | ||
"markdownlint": "^0.26.0", | ||
"prettier": "^2.7.1" | ||
} | ||
} |
@@ -6,7 +6,9 @@ # markdownlint-rule-search-replace | ||
![npm](https://img.shields.io/npm/v/markdownlint-rule-search-replace) | ||
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) | ||
## Overview | ||
In markdown files, sometimes we want to replace certain characters or patterns. | ||
In markdown files, sometimes we want to replace certain characters or patterns. | ||
For example, | ||
- curly quotes `“` to straight quotes `"` | ||
@@ -24,3 +26,4 @@ - double hyphens `--` to m-dash `—` | ||
Use following command to install | ||
Use following command to install | ||
```shell | ||
@@ -36,4 +39,5 @@ npm install markdownlint-rule-search-replace --save-dev | ||
You'll have to add a configuration entry in the .markdownlint.json file. | ||
You'll have to add a configuration entry in the .markdownlint.json file. | ||
For example, | ||
```json | ||
@@ -50,15 +54,17 @@ { | ||
"replace": "…", | ||
"skip_code": true | ||
"skipCode": true | ||
}, | ||
{ | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"search_pattern": "/“|”/g", | ||
"replace": "\"" | ||
"name": "curly-double-quotes", | ||
"message": "Do not use curly double quotes.", | ||
"searchPattern": "/“|”/g", | ||
"replace": "\"" | ||
} | ||
] | ||
] | ||
} | ||
} | ||
``` | ||
Here, | ||
- `search-replace`: The rule configuration object. | ||
@@ -70,7 +76,7 @@ - `rules`: An array of search-replace definitions. | ||
- `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 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. | ||
- `searchPattern`: 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 string, e.g. `https`. Regex properties like `$1` can be used if `searchPattern` is being used. | ||
- `skipCode`: Optional. All code(inline and block), which is inside backticks, will be skipped. | ||
Note, `search` and `search_pattern` are interchangeable. The property `search` is used if both are supplied. | ||
Note, `search` and `searchPattern` are interchangeable. The property `search` is used if both are supplied. | ||
@@ -80,8 +86,12 @@ ### Disable rule options | ||
The rule can be disabled for specific section or file. For example, if you want to disable the rule for a particular section: | ||
```md | ||
... | ||
### Markdown rules to follow | ||
The rules are: | ||
<!-- markdownlint-disable search-replace --> | ||
- Do not use three dots '...' for ellipsis. Use '…' instead. | ||
@@ -104,2 +114,3 @@ - Do not use two hyphens '--' use m-dash '—'. | ||
Use following command for [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli): | ||
```shell | ||
@@ -106,0 +117,0 @@ markdownlint test.md -r markdownlint-rule-search-replace |
188
rule.js
@@ -7,64 +7,4 @@ // @ts-check | ||
module.exports = { | ||
names: ["search-replace"], | ||
description: "Custom rule", | ||
information: new URL( | ||
"https://github.com/OnkarRuikar/markdownlint-rule-search-replace" | ||
), | ||
tags: ["replace"], | ||
const escapeForRegExp = (str) => str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); | ||
function: (params, onError) => { | ||
if (!params.config.rules) { | ||
return; | ||
} | ||
const content = params.lines.join("\n"); | ||
const lineMetadata = helpers.getLineMetadata(params); | ||
const codeRanges = helpers.codeBlockAndSpanRanges(params, lineMetadata); | ||
const htmlCommentRanges = gatHtmlCommentRanges(content, params.lines); | ||
params.config.rules.forEach((rule) => { | ||
const regex = stringToRegex(rule.search || rule.search_pattern); | ||
let result; | ||
while ((result = regex.exec(content)) !== null) { | ||
if (rule.skip_code && isCode(result.index, codeRanges, params.lines)) { | ||
continue; | ||
} | ||
if (isHTMLComment(result.index, htmlCommentRanges, params.lines)) { | ||
continue; | ||
} | ||
const match = result[0]; | ||
const [lineNo, columnNo] = getLocation(result.index, params.lines); | ||
let replacement = ""; | ||
if (rule.search) { | ||
replacement = rule.replace; | ||
} else { | ||
replacement = match.replace(new RegExp(regex), rule.replace); | ||
} | ||
onError({ | ||
lineNumber: lineNo + 1, | ||
detail: rule.name + ": " + rule.message, | ||
context: match, | ||
range: [columnNo + 1, match.length], | ||
fixInfo: { | ||
lineNumber: lineNo + 1, | ||
editColumn: columnNo + 1, | ||
deleteCount: match.length, | ||
insertText: replacement, | ||
}, | ||
}); | ||
} | ||
}); | ||
}, | ||
}; | ||
const escapeForRegExp = (str) => { | ||
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); | ||
}; | ||
/** | ||
@@ -74,2 +14,5 @@ * Converts string "/abc/ig" to new RegEx("abc", "ig"). | ||
* Converts string "abc" to new RegEx("abc", "g"). | ||
* | ||
* @param {string} str A regex string. | ||
* @returns {RegExp} A RegExp object. | ||
*/ | ||
@@ -82,3 +25,3 @@ const stringToRegex = (str) => { | ||
const flags = (str.match(/\/.+\/(.*)/) || [, "g"])[1]; | ||
const flags = (str.match(/\/.+\/(.*)/) || ["", "g"])[1]; | ||
@@ -90,2 +33,6 @@ return new RegExp(pattern, flags); | ||
* Given position in the document returns line and column number. | ||
* | ||
* @param {number} pos Position in the document. | ||
* @param {string[]} lines An array of lines. | ||
* @returns {number[]} Line number and column of the position. | ||
*/ | ||
@@ -106,5 +53,9 @@ const getLocation = (pos, lines) => { | ||
* Collect start and end position data of all code fences and html comments. | ||
* | ||
* @param {string} content Entire document text. | ||
* @param {string[]} lines An array of lines in the document. | ||
* @returns {number[][]} An array of tuple [lineNo, column, length]. | ||
*/ | ||
const gatHtmlCommentRanges = (content, lines) => { | ||
const regex = /<!--\.*-->/gm; | ||
const regex = /<!--[.\n]*-->/gm; | ||
const ranges = []; | ||
@@ -114,3 +65,15 @@ let match = null; | ||
const pos = getLocation(match.index, lines); | ||
ranges.push([...pos, match[0].length]); | ||
if (match[0].includes("\n")) { | ||
const parts = match[0].split("\n"); | ||
for (const [i, p] of parts.entries()) { | ||
if (i === 0) { | ||
ranges.push([...pos, p.length]); | ||
} else { | ||
ranges.push([pos[0] + i, 0, p.length]); | ||
} | ||
} | ||
} else { | ||
ranges.push([...pos, match[0].length]); | ||
} | ||
} | ||
@@ -121,18 +84,9 @@ return ranges; | ||
/** | ||
* Tells if the position lies inside a code block. | ||
* Tells if the position lies inside any range. | ||
* | ||
* @param {number} pos Position in the document. | ||
* @param {number[][]} ranges Array of tuple [lineNo, column, length]. | ||
* @param {string[]} lines Array of lines. | ||
* @returns {boolean} Whether the position lies in any range. | ||
*/ | ||
const isCode = (pos, ranges, lines) => { | ||
return isPartOf(pos, ranges, lines); | ||
}; | ||
/** | ||
* Tells if the position lies inside an HTML comment. | ||
*/ | ||
const isHTMLComment = (pos, ranges, lines) => { | ||
return isPartOf(pos, ranges, lines); | ||
}; | ||
/** | ||
* Tells if the position lies inside any range | ||
*/ | ||
const isPartOf = (pos, ranges, lines) => { | ||
@@ -147,1 +101,77 @@ for (const [rLine, rColumn, rLength] of ranges) { | ||
}; | ||
/** | ||
* Tells if the position lies inside a code block. | ||
* | ||
* @param {number} pos Position in the document. | ||
* @param {number[][]} ranges Array of tuple [lineNo, column, length]. | ||
* @param {string[]} lines Array of lines. | ||
* @returns {boolean} Whether the position lies in any range. | ||
*/ | ||
const isCode = (pos, ranges, lines) => isPartOf(pos, ranges, lines); | ||
/** | ||
* Tells if the position lies inside an HTML comment. | ||
* | ||
* @param {number} pos Position in the document. | ||
* @param {number[][]} ranges Array of tuple [lineNo, column, length]. | ||
* @param {string[]} lines Array of lines. | ||
* @returns {boolean} Whether the position lies in any range. | ||
*/ | ||
const isHTMLComment = (pos, ranges, lines) => isPartOf(pos, ranges, lines); | ||
module.exports = { | ||
names: ["search-replace"], | ||
description: "Custom rule", | ||
information: new URL( | ||
"https://github.com/OnkarRuikar/markdownlint-rule-search-replace" | ||
), | ||
tags: ["replace"], | ||
function: (params, onError) => { | ||
if (!params.config.rules) { | ||
return; | ||
} | ||
const content = params.lines.join("\n"); | ||
const lineMetadata = helpers.getLineMetadata(params); | ||
const codeRanges = helpers.codeBlockAndSpanRanges(params, lineMetadata); | ||
const htmlCommentRanges = gatHtmlCommentRanges(content, params.lines); | ||
for (const rule of params.config.rules) { | ||
const regex = stringToRegex(rule.search || rule.searchPattern); | ||
let result = null; | ||
while ((result = regex.exec(content)) !== null) { | ||
if (rule.skipCode && isCode(result.index, codeRanges, params.lines)) { | ||
continue; | ||
} | ||
if (isHTMLComment(result.index, htmlCommentRanges, params.lines)) { | ||
continue; | ||
} | ||
const match = result[0]; | ||
const [lineNo, columnNo] = getLocation(result.index, params.lines); | ||
let replacement = ""; | ||
replacement = rule.search | ||
? rule.replace | ||
: match.replace(new RegExp(regex), rule.replace); | ||
onError({ | ||
lineNumber: lineNo + 1, | ||
detail: rule.name + ": " + rule.message, | ||
context: `column: ${columnNo + 1} text:'${match}'`, | ||
range: [columnNo + 1, match.length], | ||
fixInfo: { | ||
lineNumber: lineNo + 1, | ||
editColumn: columnNo + 1, | ||
deleteCount: match.length, | ||
insertText: replacement, | ||
}, | ||
}); | ||
} | ||
} | ||
}, | ||
}; |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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 tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
0
133
11064
9
5
156
1