-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into de_8_14/new_terms_suppression
- Loading branch information
Showing
57 changed files
with
1,729 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
158 changes: 158 additions & 0 deletions
158
...ges/kbn-eslint-plugin-i18n/rules/formatted_message_should_start_with_the_right_id.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import { RuleTester } from 'eslint'; | ||
import { | ||
FormattedMessageShouldStartWithTheRightId, | ||
RULE_WARNING_MESSAGE, | ||
} from './formatted_message_should_start_with_the_right_id'; | ||
|
||
const tsTester = [ | ||
'@typescript-eslint/parser', | ||
new RuleTester({ | ||
parser: require.resolve('@typescript-eslint/parser'), | ||
parserOptions: { | ||
sourceType: 'module', | ||
ecmaVersion: 2018, | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}), | ||
] as const; | ||
|
||
const babelTester = [ | ||
'@babel/eslint-parser', | ||
new RuleTester({ | ||
parser: require.resolve('@babel/eslint-parser'), | ||
parserOptions: { | ||
sourceType: 'module', | ||
ecmaVersion: 2018, | ||
requireConfigFile: false, | ||
babelOptions: { | ||
presets: ['@kbn/babel-preset/node_preset'], | ||
}, | ||
}, | ||
}), | ||
] as const; | ||
|
||
for (const [name, tester] of [tsTester, babelTester]) { | ||
describe(name, () => { | ||
tester.run( | ||
'@kbn/formatted_message_should_start_with_the_right_id', | ||
FormattedMessageShouldStartWithTheRightId, | ||
{ | ||
valid: [ | ||
{ | ||
name: 'When a string literal is passed to FormattedMessage the ID attribute should start with the correct i18n identifier, and if no existing defaultMessage is passed, it should add an empty default.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: `<FormattedMessage id="xpack.observability." defaultMessage="" />`, | ||
}, | ||
{ | ||
name: 'When a string literal is passed to FormattedMessage the ID attribute should start with the correct i18n identifier, and if an existing id and defaultMessage is passed, it should leave them alone.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: `<FormattedMessage id="xpack.observability.testComponent" defaultMessage="foo" />`, | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
name: 'When a string literal is passed to FormattedMessage the ID attribute should start with the correct i18n identifier, and if no existing defaultMessage is passed, it should add an empty default.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="foo.bar.baz" />; | ||
}`, | ||
errors: [ | ||
{ | ||
line: 5, | ||
message: RULE_WARNING_MESSAGE, | ||
}, | ||
], | ||
output: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="xpack.observability.bar.baz" defaultMessage="" />; | ||
}`, | ||
}, | ||
{ | ||
name: 'When a string literal is passed to the ID attribute of <FormattedMessage /> and the root of the i18n identifier is not correct, it should keep the existing identifier but only update the right base app, and keep the default message if available.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="foo.bar.baz" defaultMessage="giraffe" />; | ||
}`, | ||
errors: [ | ||
{ | ||
line: 5, | ||
message: RULE_WARNING_MESSAGE, | ||
}, | ||
], | ||
output: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="xpack.observability.bar.baz" defaultMessage="giraffe" />; | ||
}`, | ||
}, | ||
{ | ||
name: 'When no string literal is passed to the ID attribute of <FormattedMessage /> it should start with the correct i18n identifier.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage />; | ||
}`, | ||
errors: [ | ||
{ | ||
line: 5, | ||
message: RULE_WARNING_MESSAGE, | ||
}, | ||
], | ||
output: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="xpack.observability.testComponent." defaultMessage="" />; | ||
}`, | ||
}, | ||
{ | ||
name: 'When i18n is not imported yet, the rule should add it.', | ||
filename: | ||
'/x-pack/plugins/observability_solution/observability/public/test_component.tsx', | ||
code: ` | ||
function TestComponent() { | ||
return <FormattedMessage />; | ||
}`, | ||
errors: [ | ||
{ | ||
line: 3, | ||
message: RULE_WARNING_MESSAGE, | ||
}, | ||
], | ||
output: ` | ||
import { FormattedMessage } from '@kbn/i18n-react'; | ||
function TestComponent() { | ||
return <FormattedMessage id="xpack.observability.testComponent." defaultMessage="" />; | ||
}`, | ||
}, | ||
], | ||
} | ||
); | ||
}); | ||
} |
120 changes: 120 additions & 0 deletions
120
packages/kbn-eslint-plugin-i18n/rules/formatted_message_should_start_with_the_right_id.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0 and the Server Side Public License, v 1; you may not use this file except | ||
* in compliance with, at your election, the Elastic License 2.0 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import type { TSESTree } from '@typescript-eslint/typescript-estree'; | ||
import type { Rule } from 'eslint'; | ||
import { getI18nIdentifierFromFilePath } from '../helpers/get_i18n_identifier_from_file_path'; | ||
import { getFunctionName } from '../helpers/get_function_name'; | ||
import { getI18nImportFixer } from '../helpers/get_i18n_import_fixer'; | ||
import { isTruthy } from '../helpers/utils'; | ||
|
||
export const RULE_WARNING_MESSAGE = | ||
'Id parameter passed to FormattedMessage should start with the correct i18n identifier for this file. Correct it or use the autofix suggestion.'; | ||
|
||
export const FormattedMessageShouldStartWithTheRightId: Rule.RuleModule = { | ||
meta: { | ||
type: 'suggestion', | ||
fixable: 'code', | ||
}, | ||
create(context) { | ||
const { cwd, filename, getScope, sourceCode, report } = context; | ||
|
||
return { | ||
JSXElement: (node: TSESTree.JSXElement) => { | ||
const { openingElement } = node; | ||
|
||
if (!('name' in openingElement.name) || openingElement.name.name !== 'FormattedMessage') { | ||
return; | ||
} | ||
|
||
const idAttribute = openingElement.attributes.find( | ||
(attribute) => 'name' in attribute && attribute.name.name === 'id' | ||
) as TSESTree.JSXAttribute; | ||
|
||
const identifier = | ||
idAttribute && | ||
'value' in idAttribute && | ||
idAttribute.value && | ||
'value' in idAttribute.value && | ||
typeof idAttribute.value.value === 'string' && | ||
idAttribute.value.value; | ||
|
||
const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd); | ||
const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration; | ||
const functionName = getFunctionName(functionDeclaration); | ||
|
||
// Check if i18n has already been imported into the file | ||
const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } = | ||
getI18nImportFixer({ | ||
sourceCode, | ||
translationFunction: 'FormattedMessage', | ||
}); | ||
|
||
if (!identifier) { | ||
report({ | ||
node: node as any, | ||
message: RULE_WARNING_MESSAGE, | ||
fix(fixer) { | ||
return [ | ||
fixer.replaceTextRange( | ||
node.range, | ||
`<FormattedMessage id="${i18nAppId}.${functionName}." defaultMessage="" />` | ||
), | ||
!hasI18nImportLine && rangeToAddI18nImportLine | ||
? replaceMode === 'replace' | ||
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine) | ||
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`) | ||
: null, | ||
].filter(isTruthy); | ||
}, | ||
}); | ||
} | ||
|
||
if (identifier && !identifier.startsWith(`${i18nAppId}.`)) { | ||
const oldI18nIdentifierArray = identifier.split('.'); | ||
const newI18nIdentifier = | ||
oldI18nIdentifierArray[0] === 'xpack' | ||
? `${i18nAppId}.${oldI18nIdentifierArray.slice(2).join('.')}` | ||
: `${i18nAppId}.${oldI18nIdentifierArray.slice(1).join('.')}`; | ||
|
||
const defaultMessageAttribute = openingElement.attributes.find( | ||
(attribute) => 'name' in attribute && attribute.name.name === 'defaultMessage' | ||
) as TSESTree.JSXAttribute; | ||
|
||
const defaultMessage = | ||
(defaultMessageAttribute && | ||
'value' in defaultMessageAttribute && | ||
'value' && | ||
defaultMessageAttribute.value && | ||
'value' in defaultMessageAttribute.value && | ||
typeof defaultMessageAttribute.value.value === 'string' && | ||
defaultMessageAttribute.value.value) || | ||
''; | ||
|
||
report({ | ||
node: node as any, | ||
message: RULE_WARNING_MESSAGE, | ||
fix(fixer) { | ||
return [ | ||
fixer.replaceTextRange( | ||
node.range, | ||
`<FormattedMessage id="${newI18nIdentifier}" defaultMessage="${defaultMessage}" />` | ||
), | ||
!hasI18nImportLine && rangeToAddI18nImportLine | ||
? replaceMode === 'replace' | ||
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine) | ||
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`) | ||
: null, | ||
].filter(isTruthy); | ||
}, | ||
}); | ||
} | ||
}, | ||
} as Rule.RuleListener; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.