mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Tools] Forbid i18n filter usage outside of interpolation expressions (#23982)
* [I18n] Forbid i18n filter usage outside of interpolation expressions * Add tests * Add usage examples to JSDoc
This commit is contained in:
parent
afbc9d7476
commit
a39238568a
3 changed files with 61 additions and 3 deletions
|
@ -40,4 +40,12 @@ Array [
|
|||
|
||||
exports[`dev/i18n/extractors/html throws on empty i18n-id 1`] = `"Empty \\"i18n-id\\" value in angular directive is not allowed."`;
|
||||
|
||||
exports[`dev/i18n/extractors/html throws on i18n filter usage in angular directive argument 1`] = `
|
||||
"I18n filter can be used only in interpolation expressions:
|
||||
<div
|
||||
[37m[41m [49m[39m ng-options=\\"mode as ('metricVis.colorModes.' + mode | i18n: { defaultMessage: mode }) for mode in collections.metricColorMode\\"
|
||||
></div>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`dev/i18n/extractors/html throws on missing i18n-default-message attribute 1`] = `"Empty defaultMessage in angular directive is not allowed (\\"message-id\\")."`;
|
||||
|
|
|
@ -36,9 +36,11 @@ import { DEFAULT_MESSAGE_KEY, CONTEXT_KEY, VALUES_KEY } from '../constants';
|
|||
import { createFailError } from '../../run';
|
||||
|
||||
/**
|
||||
* Find all substrings of "{{ any text }}" pattern
|
||||
* Find all substrings of "{{ any text }}" pattern allowing '{' and '}' chars in single quote strings
|
||||
*
|
||||
* Example: `{{ ::'message.id' | i18n: { defaultMessage: 'Message with {{curlyBraces}}' } }}`
|
||||
*/
|
||||
const ANGULAR_EXPRESSION_REGEX = /\{\{+([\s\S]*?)\}\}+/g;
|
||||
const ANGULAR_EXPRESSION_REGEX = /{{([^{}]|({([^']|('([^']|(\\'))*'))*?}))*}}+/g;
|
||||
|
||||
const I18N_FILTER_MARKER = '| i18n: ';
|
||||
|
||||
|
@ -113,7 +115,12 @@ function parseIdExpression(expression) {
|
|||
const stringNode = [...traverseNodes(ast.program.directives)].find(node =>
|
||||
isDirectiveLiteral(node)
|
||||
);
|
||||
return stringNode ? formatJSString(stringNode.value) : null;
|
||||
|
||||
if (!stringNode) {
|
||||
throw createFailError(`Message id should be a string literal, but got: \n${expression}`);
|
||||
}
|
||||
|
||||
return formatJSString(stringNode.value);
|
||||
}
|
||||
|
||||
function trimCurlyBraces(string) {
|
||||
|
@ -148,7 +155,40 @@ function trimOneTimeBindingOperator(string) {
|
|||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove interpolation expressions from angular and throw on `| i18n:` substring.
|
||||
*
|
||||
* Correct usage: `<p aria-label="{{ ::'namespace.id' | i18n: { defaultMessage: 'Message' } }}"></p>`.
|
||||
*
|
||||
* Incorrect usage: `ng-options="mode as ('metricVis.colorModes.' + mode | i18n: { defaultMessage: mode }) for mode in collections.metricColorMode"`
|
||||
*
|
||||
* @param {string} string html content
|
||||
*/
|
||||
function validateI18nFilterUsage(string) {
|
||||
const stringWithoutExpressions = string.replace(ANGULAR_EXPRESSION_REGEX, '');
|
||||
const i18nMarkerPosition = stringWithoutExpressions.indexOf(I18N_FILTER_MARKER);
|
||||
|
||||
if (i18nMarkerPosition === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linesCount = (stringWithoutExpressions.slice(0, i18nMarkerPosition).match(/\n/g) || [])
|
||||
.length;
|
||||
|
||||
const errorWithContext = createParserErrorMessage(string, {
|
||||
loc: {
|
||||
line: linesCount + 1,
|
||||
column: 0,
|
||||
},
|
||||
message: 'I18n filter can be used only in interpolation expressions',
|
||||
});
|
||||
|
||||
throw createFailError(errorWithContext);
|
||||
}
|
||||
|
||||
function* getFilterMessages(htmlContent) {
|
||||
validateI18nFilterUsage(htmlContent);
|
||||
|
||||
const expressions = (htmlContent.match(ANGULAR_EXPRESSION_REGEX) || [])
|
||||
.filter(expression => expression.includes(I18N_FILTER_MARKER))
|
||||
.map(trimCurlyBraces);
|
||||
|
|
|
@ -71,6 +71,16 @@ describe('dev/i18n/extractors/html', () => {
|
|||
<p
|
||||
i18n-id="message-id"
|
||||
></p>
|
||||
`);
|
||||
|
||||
expect(() => extractHtmlMessages(source).next()).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('throws on i18n filter usage in angular directive argument', () => {
|
||||
const source = Buffer.from(`\
|
||||
<div
|
||||
ng-options="mode as ('metricVis.colorModes.' + mode | i18n: { defaultMessage: mode }) for mode in collections.metricColorMode"
|
||||
></div>
|
||||
`);
|
||||
|
||||
expect(() => extractHtmlMessages(source).next()).toThrowErrorMatchingSnapshot();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue