mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
ci: Add rule dangerously-use-html-string-missing
(no-changelog) (#6299)
This commit is contained in:
parent
fe937a0ee3
commit
96144bd983
|
@ -14,6 +14,15 @@ module.exports = {
|
||||||
|
|
||||||
ignorePatterns: ['**/*.js', '**/*.d.ts', 'vite.config.ts', '**/*.ts.snap'],
|
ignorePatterns: ['**/*.js', '**/*.d.ts', 'vite.config.ts', '**/*.ts.snap'],
|
||||||
|
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['src/**/*.vue'],
|
||||||
|
rules: {
|
||||||
|
'n8n-local-rules/dangerously-use-html-string-missing': 'error',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file contains any locally defined ESLint rules. They are picked up by
|
* This file contains any locally defined ESLint rules. They are picked up by
|
||||||
* eslint-plugin-n8n-local-rules and exposed as 'n8n-local-rules/<rule-name>'.
|
* eslint-plugin-n8n-local-rules and exposed as 'n8n-local-rules/<rule-name>'.
|
||||||
|
@ -239,6 +241,118 @@ module.exports = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'dangerously-use-html-string-missing': {
|
||||||
|
meta: {
|
||||||
|
type: 'error',
|
||||||
|
docs: {
|
||||||
|
description:
|
||||||
|
'Calls to the `showToast` and `showMessage` methods must include `dangerouslyUseHTMLString: true` when at least one of the values in `title` or `message` contains HTML.',
|
||||||
|
recommended: 'error',
|
||||||
|
/**
|
||||||
|
* @note This rule does not yet cover cases where the result of calling
|
||||||
|
* `this.$locale.someMethod()` is assigned to a variable that is then
|
||||||
|
* assigned to `title or `message`, e.g. `message: errorMessage`.
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create(context) {
|
||||||
|
const cwd = context.getCwd();
|
||||||
|
const locale = 'src/plugins/i18n/locales/en.json';
|
||||||
|
|
||||||
|
const LOCALE_NAMESPACE = '$locale';
|
||||||
|
const LOCALE_FILEPATH = cwd.endsWith('editor-ui')
|
||||||
|
? path.join(cwd, locale)
|
||||||
|
: path.join(cwd, 'packages/editor-ui', locale);
|
||||||
|
|
||||||
|
let LOCALE_MAP;
|
||||||
|
|
||||||
|
try {
|
||||||
|
LOCALE_MAP = JSON.parse(require('fs').readFileSync(LOCALE_FILEPATH));
|
||||||
|
} catch {
|
||||||
|
console.log(
|
||||||
|
'[dangerously-use-html-string-missing] Failed to load locale map, skipping rule...',
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const METHODS_POSSIBLY_REQUIRING_HTML = new Set(['showToast', 'showMessage']);
|
||||||
|
const PROPERTIES_POSSIBLY_CONTAINING_HTML = new Set(['title', 'message']);
|
||||||
|
const USE_HTML_PROPERTY = 'dangerouslyUseHTMLString';
|
||||||
|
|
||||||
|
const isMethodPossiblyRequiringRawHtml = (node) =>
|
||||||
|
node.callee.type === 'MemberExpression' &&
|
||||||
|
node.callee.object.type === 'ThisExpression' &&
|
||||||
|
node.callee.property.type === 'Identifier' &&
|
||||||
|
METHODS_POSSIBLY_REQUIRING_HTML.has(node.callee.property.name) &&
|
||||||
|
node.arguments.length === 1 &&
|
||||||
|
node.arguments.at(0).type === 'ObjectExpression';
|
||||||
|
|
||||||
|
const isPropertyWithLocaleStringAsValue = (property) =>
|
||||||
|
property.key.type === 'Identifier' &&
|
||||||
|
PROPERTIES_POSSIBLY_CONTAINING_HTML.has(property.key.name) &&
|
||||||
|
property.value.type === 'CallExpression' &&
|
||||||
|
property.value.callee.type === 'MemberExpression' &&
|
||||||
|
property.value.callee.object.type === 'MemberExpression' &&
|
||||||
|
property.value.callee.object.property.type === 'Identifier' &&
|
||||||
|
property.value.callee.object.property.name === LOCALE_NAMESPACE &&
|
||||||
|
property.value.arguments.length >= 1 &&
|
||||||
|
property.value.arguments.at(0).type === 'Literal' &&
|
||||||
|
typeof property.value.arguments.at(0).value === 'string';
|
||||||
|
|
||||||
|
const containsHtml = (str) => {
|
||||||
|
let insideTag = false;
|
||||||
|
|
||||||
|
for (let char of str) {
|
||||||
|
if (char === '<') {
|
||||||
|
insideTag = true;
|
||||||
|
} else if (char === '>') {
|
||||||
|
if (insideTag) return true;
|
||||||
|
insideTag = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
CallExpression(node) {
|
||||||
|
if (!isMethodPossiblyRequiringRawHtml(node)) return;
|
||||||
|
|
||||||
|
const arg = node.arguments.at(0);
|
||||||
|
|
||||||
|
const hasArgWitHtml = arg.properties
|
||||||
|
.reduce(
|
||||||
|
(acc, p) =>
|
||||||
|
isPropertyWithLocaleStringAsValue(p)
|
||||||
|
? [...acc, p.value.arguments.at(0).value]
|
||||||
|
: acc,
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.some((i) => containsHtml(LOCALE_MAP[i]));
|
||||||
|
|
||||||
|
if (!hasArgWitHtml) return;
|
||||||
|
|
||||||
|
const hasRawHtmlPropertyAsTrue = arg.properties.some(
|
||||||
|
(p) =>
|
||||||
|
p.key.type === 'Identifier' &&
|
||||||
|
p.key.name === USE_HTML_PROPERTY &&
|
||||||
|
p.value.type === 'Literal' &&
|
||||||
|
p.value.value === true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasRawHtmlPropertyAsTrue) return;
|
||||||
|
|
||||||
|
const methodName = node.callee.property.name;
|
||||||
|
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message: `Set \`${USE_HTML_PROPERTY}: true\` in the argument to \`${methodName}\`. At least one of the values in \`title\` or \`message\` contains HTML.`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const isJsonParseCall = (node) =>
|
const isJsonParseCall = (node) =>
|
||||||
|
|
|
@ -9,6 +9,8 @@ module.exports = {
|
||||||
...sharedOptions(__dirname, 'frontend'),
|
...sharedOptions(__dirname, 'frontend'),
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
|
'n8n-local-rules/dangerously-use-html-string-missing': 'off',
|
||||||
|
|
||||||
// TODO: Remove these
|
// TODO: Remove these
|
||||||
'import/no-default-export': 'off',
|
'import/no-default-export': 'off',
|
||||||
'import/order': 'off',
|
'import/order': 'off',
|
||||||
|
|
Loading…
Reference in a new issue