Skip to content

Commit

Permalink
feat: add markupOnly option
Browse files Browse the repository at this point in the history
  • Loading branch information
edvardchen committed Jun 3, 2020
1 parent 79ac81b commit 7bb225c
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 39 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,26 @@ switch (type) {

### Options

### markupOnly

If `markupOnly` option turn on, only JSX text and strings used as JSX attributes will be validated.

JSX text:

```jsx
// incorrect
<div>hello world</div>
<div>{"hello world"}</div>
```

Strings as JSX attribute:

```jsx
// incorrect
<div foo="foo"></div>
<div foo={"foo"}></div>
```

#### ignore

The `ignore` option specifies exceptions not to check for
Expand Down
96 changes: 59 additions & 37 deletions lib/rules/no-literal-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ module.exports = {
items: {
type: 'string'
}
},
markupOnly: {
type: 'boolean'
}
},
additionalProperties: false
Expand Down Expand Up @@ -152,6 +155,47 @@ module.exports = {
if (program && esTreeNodeToTSNodeMap)
typeChecker = program.getTypeChecker();

function validateLiteral(node) {
// visited and passed linting
if (visited.has(node)) return;
const trimed = node.value.trim();
if (!trimed) return;

const { parent } = node;

// allow statements like const a = "FOO"
if (isUpperCase(trimed)) return;

if (match(trimed)) return;

//
// TYPESCRIPT
//

if (typeChecker) {
const tsNode = esTreeNodeToTSNodeMap.get(node);
const typeObj = typeChecker.getTypeAtLocation(tsNode.parent);

// var a: 'abc' = 'abc'
if (typeObj.isStringLiteral()) {
return;
}

// var a: 'abc' | 'name' = 'abc'
if (typeObj.isUnion()) {
const found = typeObj.types.some(item => {
if (item.isStringLiteral() && item.value === node.value) {
return true;
}
});
if (found) return;
}
}
// • • • • •

context.report({ node, message });
}

const scriptVisitor = {
//
// ─── EXPORT AND IMPORT ───────────────────────────────────────────
Expand Down Expand Up @@ -186,6 +230,18 @@ module.exports = {
scriptVisitor.JSXText(node);
},

'JSXExpressionContainer > Literal:exit'(node) {
if (option && option.markupOnly) {
validateLiteral(node);
}
},

'JSXAttribute > Literal:exit'(node) {
if (option && option.markupOnly) {
validateLiteral(node);
}
},

'JSXAttribute Literal'(node) {
const parent = getNearestAncestor(node, 'JSXAttribute');
const attrName = parent.name.name;
Expand Down Expand Up @@ -283,44 +339,10 @@ module.exports = {
},

'Literal:exit'(node) {
// visited and passed linting
if (visited.has(node)) return;
const trimed = node.value.trim();
if (!trimed) return;

const { parent } = node;

// allow statements like const a = "FOO"
if (isUpperCase(trimed)) return;

if (match(trimed)) return;

//
// TYPESCRIPT
//

if (typeChecker) {
const tsNode = esTreeNodeToTSNodeMap.get(node);
const typeObj = typeChecker.getTypeAtLocation(tsNode.parent);

// var a: 'abc' = 'abc'
if (typeObj.isStringLiteral()) {
return;
}

// var a: 'abc' | 'name' = 'abc'
if (typeObj.isUnion()) {
const found = typeObj.types.some(item => {
if (item.isStringLiteral() && item.value === node.value) {
return true;
}
});
if (found) return;
}
if (option && option.markupOnly) {
return;
}
// • • • • •

context.report({ node, message });
validateLiteral(node);
}
};

Expand Down
24 changes: 22 additions & 2 deletions tests/lib/rules/no-literal-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,23 @@ ruleTester.run('no-literal-string', rule, {
},
{ code: '<div role="button"></div>' },
{ code: '<img src="./image.png" />' },
{ code: '<A style="bar" />', errors },
{ code: '<A style="bar" />' },
{ code: '<button type="button" for="form-id" />' },
{ code: '<DIV foo="bar" />', options: [{ ignoreAttribute: ['foo'] }] },
{ code: '<Trans>foo</Trans>' },
{ code: '<Trans><span>bar</span></Trans>' }
{ code: '<Trans><span>bar</span></Trans>' },
{ code: 'a + "b"', options: [{ markupOnly: true }] },
{ code: '<div>{import("abc")}</div>', options: [{ markupOnly: true }] },
{
code: '<div>{[].map(item=>"abc")}</div>',
options: [{ markupOnly: true }]
},
{ code: '<div>{"hello" + "world"}</div>', options: [{ markupOnly: true }] },
{ code: '<DIV foo="FOO" />', options: [{ markupOnly: true }] },
{
code: '<DIV foo="bar" />',
options: [{ markupOnly: true, ignoreAttribute: ['foo'] }]
}
],

invalid: [
Expand All @@ -119,8 +131,16 @@ ruleTester.run('no-literal-string', rule, {
},
// JSX
{ code: '<div>foo</div>', errors },
{ code: '<div>foo</div>', options: [{ markupOnly: true }], errors },
{ code: '<div>FOO</div>', errors },
{
code: '<div>{"hello world"}</div>',
options: [{ markupOnly: true }],
errors
},
{ code: '<DIV foo="bar" />', errors },
{ code: '<DIV foo="bar" />', options: [{ markupOnly: true }], errors },
{ code: '<DIV foo={"bar"} />', options: [{ markupOnly: true }], errors },
{ code: '<img src="./image.png" alt="some-image" />', errors },
{ code: '<button aria-label="Close" type="button" />', errors }
]
Expand Down

0 comments on commit 7bb225c

Please sign in to comment.