Skip to content

Commit

Permalink
Merge pull request #134 from fants613/feat/support-vue
Browse files Browse the repository at this point in the history
support validate literal strings in vue component
  • Loading branch information
edvardchen authored Nov 24, 2024
2 parents a42ec3e + d972a01 commit 06854b6
Show file tree
Hide file tree
Showing 6 changed files with 2,691 additions and 2,086 deletions.
13 changes: 9 additions & 4 deletions docs/rules/no-literal-string.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ type MySchema = {
exclude?: string[];
};
} & {
mode?: 'jsx-text-only' | 'jsx-only' | 'all';
framework: 'react' | 'vue';
mode?: 'jsx-text-only' | 'jsx-only' | 'all' | 'vue-template-ony';
message?: string;
'should-validate-template'?: boolean;
};
Expand Down Expand Up @@ -118,10 +119,14 @@ const message = 'foob';

### Other options

- `framework` specifies the type of framework currently in use.
- `react` It defaults to 'react' which means you want to validate react component
- `vue` If you want to validate vue component, can set the value to be this
- `mode` provides a straightforward way to decides the range you want to validate literal strings.
It defaults to `jsx-text-only` which only forbids to write plain text in JSX markup
- `jsx-only` validates the JSX attributes as well
- `all` validates all literal strings
It defaults to `jsx-text-only` which only forbids to write plain text in JSX markup,available when framework option is 'react'
- `jsx-only` validates the JSX attributes as well,available when framework option is 'react'
- `all` validates all literal strings,available when the value of the framework option is 'react' and 'vue'
- `vue-template-only`, only validate vue component template part,available when framework option value is 'vue'.
- `message` defines the custom error message
- `should-validate-template` decides if we should validate the string templates

Expand Down
1 change: 1 addition & 0 deletions lib/options/defaults.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
framework: 'react',
mode: 'jsx-text-only',
'jsx-components': {
include: [],
Expand Down
8 changes: 6 additions & 2 deletions lib/options/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"framework":{
"type":"string",
"enum": ["react","vue"]
},
"mode": {
"type": "string",
"enum": ["jsx-text-only", "jsx-only", "all"]
"enum": ["jsx-text-only", "jsx-only", "all","vue-template-only"]
},
"jsx-components": {
"type": "object",
Expand Down Expand Up @@ -50,4 +54,4 @@
"message": { "type": "string" },
"should-validate-template": { "type": "boolean" }
}
}
}
102 changes: 83 additions & 19 deletions lib/rules/no-literal-string.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ function isValidLiteral(options, { value }) {
if (shouldSkip(options.words, trimed)) return true;
}

/**
* @param {VDirective | VAttribute} node
* @returns {string | null}
*/
function getAttributeName(node) {
if (!node.directive) {
return node.key.rawName;
}

if (
(node.key.name.name === 'bind' || node.key.name.name === 'model') &&
node.key.argument &&
node.key.argument.type === 'VIdentifier'
) {
return node.key.argument.rawName;
}

return null;
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -58,9 +78,13 @@ module.exports = {
mode,
'should-validate-template': validateTemplate,
message,
framework,
} = options;
const onlyValidateJSX = ['jsx-only', 'jsx-text-only'].includes(mode);

const onlyValidateVueTemplate =
framework === 'vue' && mode === 'vue-template-only';

//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
Expand Down Expand Up @@ -210,6 +234,22 @@ module.exports = {
},
// ─────────────────────────────────────────────────────────────────

//
// ─── Vue ──────────────────────────────────────────────────
//
VElement(node) {
indicatorStack.push(
shouldSkip(options['jsx-components'], node.rawName)
);
},
'VElement:exit': endIndicator,
VAttribute(node) {
const attrName = getAttributeName(node);
indicatorStack.push(shouldSkip(options['jsx-attributes'], attrName));
},
'VAttribute:exit': endIndicator,
// ─────────────────────────────────────────────────────────────────

//
// ─── TYPESCRIPT ──────────────────────────────────────────────────
//
Expand Down Expand Up @@ -284,12 +324,6 @@ module.exports = {
);
},
'TaggedTemplateExpression:exit': endIndicator,

'SwitchCase > Literal'(node) {
indicatorStack.push(true);
},
'SwitchCase > Literal:exit': endIndicator,

'AssignmentExpression[left.type="MemberExpression"]'(node) {
// allow Enum['value']
indicatorStack.push(
Expand All @@ -299,20 +333,12 @@ module.exports = {
'AssignmentExpression[left.type="MemberExpression"]:exit'(node) {
endIndicator();
},
'MemberExpression > Literal'(node) {
// allow Enum['value']
indicatorStack.push(true);
},
'MemberExpression > Literal:exit'(node) {
endIndicator();
},

TemplateLiteral(node) {
if (!validateTemplate) {
return;
}

if (filterOutJSX(node)) {
if (framework === 'react' && filterOutJSX(node)) {
return;
}

Expand All @@ -324,16 +350,39 @@ module.exports = {
return true; // break
});
},
Literal(node) {
// allow Enum['value'] and literal that follows the 'case' keyword in a switch statement.
if (['MemberExpression', 'SwitchCase'].includes(node?.parent?.type)) {
return;
}

'Literal:exit'(node) {
if (filterOutJSX(node)) {
if (framework === 'react' && filterOutJSX(node)) {
return;
}

if (onlyValidateVueTemplate) {
const parents = context.getAncestors();
if (
parents.length &&
parents.every(
item =>
![
'VElement',
'VAttribute',
'VText',
'VExpressionContainer',
].includes(item.type)
)
) {
return true;
}
}

// ignore `var a = { "foo": 123 }`
if (node.parent.key === node) {
return;
}

validateBeforeReport(node);
},
};
Expand All @@ -345,14 +394,29 @@ module.exports = {
VText(node) {
scriptVisitor['JSXText'](node);
},
VLiteral(node) {
scriptVisitor['JSXText'](node);
},
VElement(node) {
scriptVisitor['VElement'](node);
},
'VElement:exit'(node) {
scriptVisitor['VElement:exit'](node);
},
VAttribute(node) {
scriptVisitor['VAttribute'](node);
},
'VAttribute:exit'(node) {
scriptVisitor['VAttribute:exit'](node);
},
'VExpressionContainer CallExpression'(node) {
scriptVisitor['CallExpression'](node);
},
'VExpressionContainer CallExpression:exit'(node) {
scriptVisitor['CallExpression:exit'](node);
},
'VExpressionContainer Literal:exit'(node) {
scriptVisitor['Literal:exit'](node);
'VExpressionContainer Literal'(node) {
scriptVisitor['Literal'](node);
},
},
scriptVisitor
Expand Down
Loading

0 comments on commit 06854b6

Please sign in to comment.