-
-
Notifications
You must be signed in to change notification settings - Fork 355
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
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 using `’` instead of `'` to avoid escaping. | ||
|
||
This rule is fixable. | ||
|
||
_This rule only reports one pattern per node._ | ||
|
||
## 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-context": [ | ||
"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.