Skip to content

Commit

Permalink
Add string-content rule
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed Jan 3, 2020
1 parent cea1346 commit 083bda0
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 0 deletions.
103 changes: 103 additions & 0 deletions rules/string-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use strict';
const getDocumentationUrl = require('./utils/get-documentation-url');

const MAX_REPLACEMENTS = 3;

function message(replacements) {
replacements = replacements.map(({match, suggest}) => `\`${suggest}\` over \`${match}\``);

const last = replacements.pop();

const hasMore = replacements.length >= MAX_REPLACEMENTS - 1;

let message = 'Prefer ';
if (replacements.length !== 0) {
message += replacements.slice(0, MAX_REPLACEMENTS - 1).join(' ,');

if (hasMore) {
message += ' …';
}

message += ' and ';
}

return `${message}${last}`;
}

const create = context => {
const {patterns = []} = context.options[0] || {};

if (patterns.length === 0) {
return {};
}

return {
Literal: node => {
const {value} = node;

if (typeof value !== 'string') {
return;
}

const reportedPatterns = patterns.filter(({match}) => value.includes(match));

if (reportedPatterns.length === 0) {
return;
}

const fixed = reportedPatterns.filter(({fix}) => fix).reduce((fixed, {match, suggest}) => fixed.split(match).join(suggest), value);
const quote = node.raw[0];
const escaped = fixed.replace(new RegExp(quote, 'g'), `\\${quote}`);
const fix = fixed === value ?
undefined :
fixer => fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], escaped);

context.report({
node,
message: message(reportedPatterns),
fix
});
}
};
};

const patternSchema = {
type: 'object',
properties: {
match: {
type: 'string',
require: true
},
suggest: {
type: 'string',
require: true
},
fix: {
type: 'boolean',
default: true
}
},
additionalProperties: false
};
const schema = [{
type: 'object',
properties: {
patterns: {
type: 'array',
items: patternSchema
}
},
additionalProperties: false
}];

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
92 changes: 92 additions & 0 deletions test/string-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import test from 'ava';
import avaRuleTester from 'eslint-ava-rule-tester';
import rule from '../rules/string-content';

const ruleTester = avaRuleTester(test, {
env: {
es6: true
}
});

const patterns = {
unicorn: {
match: 'unicorn',
suggest: 'πŸ¦„'
},
awesome: {
match: 'awesome',
suggest: '😎'
}
};

function invalidCase({
code,
message,
patterns,
output = code
}) {
return {
code,
output,
options: [{patterns}],
errors: [{message}]
};
}

ruleTester.run('string-content', rule, {
valid: [
'const foo = \'πŸ¦„\''
],
invalid: [
{
code: 'const foo = \'unicorn\'',
output: 'const foo = \'πŸ¦„\'',
message: 'Prefer `πŸ¦„` over `unicorn`',
patterns: [patterns.unicorn]
},
// Escape single quote
{
code: 'const foo = \'_\'',
output: 'const foo = \'\\\'"\'',
message: 'Prefer `\'"` over `_`',
patterns: [{match: '_', suggest: '\'"'}]
},
// Escape double quote
{
code: 'const foo = "_"',
output: 'const foo = "\'\\""',
message: 'Prefer `\'"` over `_`',
patterns: [{match: '_', suggest: '\'"'}]
},
// Not fix
{
code: 'const foo = \'unicorn\'',
message: 'Prefer `πŸ¦„` over `unicorn`',
patterns: [{...patterns.unicorn, fix: false}]
},
// Multi patterns
{
code: 'const foo = \'unicorn is awesome\'',
output: 'const foo = \'πŸ¦„ is 😎\'',
message: 'Prefer `πŸ¦„` over `unicorn` and `😎` over `awesome`',
patterns: [patterns.unicorn, patterns.awesome]
},
// Multi patterns, Not fix `awesome`
{
code: 'const foo = \'unicorn is awesome\'',
output: 'const foo = \'πŸ¦„ is awesome\'',
message: 'Prefer `πŸ¦„` over `unicorn` and `😎` over `awesome`',
patterns: [patterns.unicorn, {...patterns.awesome, fix: false}]
},
// Many patterns
{
code: 'const foo = \'abcdefghijklmnopqrstuvwxyz\'',
output: 'const foo = \'ABCDEFGHIJKLMNOPQRSTUVWXYZ\'',
message: 'Prefer `A` over `a` ,`B` over `b` … and `Z` over `z`',
patterns: Array.from({length: 26}, (_, index) => ({
match: String.fromCharCode('a'.charCodeAt(0) + index),
suggest: String.fromCharCode('A'.charCodeAt(0) + index)
}))
}
].map(invalidCase)
});

0 comments on commit 083bda0

Please sign in to comment.