Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
- Loading branch information
1 parent
49c4acf
commit 0972a89
Showing
9 changed files
with
464 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Enforce better string content | ||
|
||
Enforce certain things about the contents of strings. For example, you can enforce using `’` instead of `'` to avoid escaping. Or you could block some words. The possibilities are endless. | ||
|
||
This rule is fixable. | ||
|
||
*It only reports one pattern per AST node at the time.* | ||
|
||
## Fail | ||
|
||
```js | ||
const foo = 'Someone\'s coming!'; | ||
``` | ||
|
||
## Pass | ||
|
||
```js | ||
const foo = 'Someone’s coming!'; | ||
``` | ||
|
||
## Options | ||
|
||
Type: `object` | ||
|
||
### patterns | ||
|
||
Type: `object` | ||
|
||
Extend [default patterns](#default-pattern). | ||
|
||
The example below: | ||
|
||
- Disables the default `'` → `’` replacement. | ||
- Adds a custom `unicorn` → `🦄` replacement. | ||
- Adds a custom `awesome` → `😎` replacement and a custom message. | ||
- Adds a custom `cool` → `😎` replacement, but disables auto fix. | ||
|
||
```json | ||
{ | ||
"unicorn/string-content": [ | ||
"error", | ||
{ | ||
"patterns": { | ||
"'": false, | ||
"unicorn": "🦄", | ||
"awesome": { | ||
"suggest": "😎", | ||
"message": "Please use `😎` instead of `awesome`." | ||
}, | ||
"cool": { | ||
"suggest": "😎", | ||
"fix": false | ||
} | ||
} | ||
} | ||
] | ||
} | ||
``` | ||
|
||
The key of `patterns` is treated as a regex, so you must escape special characters. | ||
|
||
For example, if you want to enforce `...` → `…`: | ||
|
||
```json | ||
{ | ||
"patterns": { | ||
"\\.\\.\\.": "…" | ||
} | ||
} | ||
``` | ||
|
||
## Default Pattern | ||
|
||
```json | ||
{ | ||
"'": "’" | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
'use strict'; | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const quoteString = require('./utils/quote-string'); | ||
const replaceTemplateElement = require('./utils/replace-template-element'); | ||
const escapeTemplateElementRaw = require('./utils/escape-template-element-raw'); | ||
|
||
const defaultPatterns = { | ||
'\'': '’' | ||
}; | ||
|
||
const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.'; | ||
|
||
function getReplacements(patterns) { | ||
return Object.entries({ | ||
...defaultPatterns, | ||
...patterns | ||
}) | ||
.filter(([, options]) => options !== false) | ||
.map(([match, options]) => { | ||
if (typeof options === 'string') { | ||
options = { | ||
suggest: options | ||
}; | ||
} | ||
|
||
return { | ||
match, | ||
regex: new RegExp(match, 'gu'), | ||
fix: true, | ||
...options | ||
}; | ||
}); | ||
} | ||
|
||
const create = context => { | ||
const {patterns} = { | ||
patterns: {}, | ||
...context.options[0] | ||
}; | ||
const replacements = getReplacements(patterns); | ||
|
||
if (replacements.length === 0) { | ||
return {}; | ||
} | ||
|
||
return { | ||
'Literal, TemplateElement': node => { | ||
const {type} = node; | ||
|
||
let string; | ||
if (type === 'Literal') { | ||
string = node.value; | ||
if (typeof string !== 'string') { | ||
return; | ||
} | ||
} else { | ||
string = node.value.raw; | ||
} | ||
|
||
if (!string) { | ||
return; | ||
} | ||
|
||
const replacement = replacements.find(({regex}) => regex.test(string)); | ||
|
||
if (!replacement) { | ||
return; | ||
} | ||
|
||
const {fix, message = defaultMessage, match, suggest} = replacement; | ||
const problem = { | ||
node, | ||
message, | ||
data: { | ||
match, | ||
suggest | ||
} | ||
}; | ||
|
||
if (!fix) { | ||
context.report(problem); | ||
return; | ||
} | ||
|
||
const fixed = string.replace(replacement.regex, suggest); | ||
if (type === 'Literal') { | ||
problem.fix = fixer => fixer.replaceText( | ||
node, | ||
quoteString(fixed, node.raw[0]) | ||
); | ||
} else { | ||
problem.fix = fixer => replaceTemplateElement( | ||
fixer, | ||
node, | ||
escapeTemplateElementRaw(fixed) | ||
); | ||
} | ||
|
||
context.report(problem); | ||
} | ||
}; | ||
}; | ||
|
||
const schema = [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
patterns: { | ||
type: 'object', | ||
additionalProperties: { | ||
anyOf: [ | ||
{ | ||
enum: [ | ||
false | ||
] | ||
}, | ||
{ | ||
type: 'string' | ||
}, | ||
{ | ||
type: 'object', | ||
required: [ | ||
'suggest' | ||
], | ||
properties: { | ||
suggest: { | ||
type: 'string' | ||
}, | ||
fix: { | ||
type: 'boolean' | ||
// Default: true | ||
}, | ||
message: { | ||
type: 'string' | ||
// Default: '' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}} | ||
}, | ||
additionalProperties: false | ||
} | ||
]; | ||
|
||
module.exports = { | ||
create, | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
url: getDocumentationUrl(__filename) | ||
}, | ||
fixable: 'code', | ||
schema | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
'use strict'; | ||
|
||
module.exports = string => string.replace( | ||
/(?<=(?:^|[^\\])(?:\\\\)*)(?<symbol>(?:`|\$(?={)))/g, | ||
'\\$<symbol>' | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,17 @@ | ||
'use strict'; | ||
|
||
/** | ||
Escape apostrophe and wrap the result in single quotes. | ||
Escape string and wrap the result in quotes. | ||
@param {string} string - The string to be quoted. | ||
@returns {string} - The quoted string. | ||
@param {string} quote - The quote character. | ||
@returns {string} - The quoted and escaped string. | ||
*/ | ||
module.exports = string => `'${string.replace(/'/g, '\\\'')}'`; | ||
module.exports = (string, quote = '\'') => { | ||
const escaped = string | ||
.replace(/\\/g, '\\\\') | ||
.replace(/\r/g, '\\r') | ||
.replace(/\n/g, '\\n') | ||
.replace(new RegExp(quote, 'g'), `\\${quote}`); | ||
return quote + escaped + quote; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.