Skip to content

Commit

Permalink
feat(eslint-plugin-template): [i18n] option to require i18n metadata …
Browse files Browse the repository at this point in the history
…meaning (#1234)
  • Loading branch information
abaran30 committed Dec 7, 2022
1 parent abe4e0a commit 4ef0290
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 4 deletions.
217 changes: 217 additions & 0 deletions packages/eslint-plugin-template/docs/rules/i18n.md
Expand Up @@ -53,6 +53,7 @@ interface Options {
ignoreAttributes?: string[];
ignoreTags?: string[];
requireDescription?: boolean;
requireMeaning?: boolean;
}

```
Expand Down Expand Up @@ -611,6 +612,132 @@ interface Options {
~~~~~
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"checkId": false,
"requireMeaning": true
}
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<h1 i18n>Hello</h1>
~~~~~~~~~~~~~~~~~~~
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"checkId": false,
"requireDescription": true,
"requireMeaning": true
}
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<h1 i18n="An introduction header for this sample">Hello</h1>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"requireDescription": true,
"requireMeaning": true
}
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<h1 i18n="An introduction header for this sample@@custom-id">Hello</h1>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"checkId": false,
"requireDescription": true,
"requireMeaning": true
}
]
}
}
```

<br>

#### ❌ Invalid Code

```html
<h1 i18n="|An introduction header for this sample">Hello</h1>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

</details>

<br>
Expand Down Expand Up @@ -1479,6 +1606,96 @@ interface Options {
<span i18n="@@custom-id">Some text to translate</span>
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"checkId": false,
"requireDescription": true,
"requireMeaning": true
}
]
}
}
```

<br>

#### ✅ Valid Code

```html
<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"checkId": false,
"requireMeaning": true
}
]
}
}
```

<br>

#### ✅ Valid Code

```html
<h1 i18n="site header|">Hello i18n!</h1>
```

<br>

---

<br>

#### Custom Config

```json
{
"rules": {
"@angular-eslint/template/i18n": [
"error",
{
"requireMeaning": true
}
]
}
}
```

<br>

#### ✅ Valid Code

```html
<h1 i18n="site header|@@custom-id">Hello i18n!</h1>
```

</details>

<br>
26 changes: 22 additions & 4 deletions packages/eslint-plugin-template/src/rules/i18n.ts
Expand Up @@ -68,6 +68,7 @@ type Options = [
readonly ignoreAttributes?: readonly string[];
readonly ignoreTags?: readonly string[];
readonly requireDescription?: boolean;
readonly requireMeaning?: boolean;
},
];
export type MessageIds =
Expand All @@ -77,7 +78,8 @@ export type MessageIds =
| 'i18nCustomIdOnElement'
| 'i18nDuplicateCustomId'
| 'suggestAddI18nAttribute'
| 'i18nMissingDescription';
| 'i18nMissingDescription'
| 'i18nMissingMeaning';
type StronglyTypedElement = Omit<TmplAstElement, 'i18n'> & {
i18n: Message;
parent?: AST;
Expand Down Expand Up @@ -168,6 +170,10 @@ export default createESLintRule<Options, MessageIds>({
type: 'boolean',
default: DEFAULT_OPTIONS.requireDescription,
},
requireMeaning: {
type: 'boolean',
default: DEFAULT_OPTIONS.requireMeaning,
},
},
additionalProperties: false,
},
Expand All @@ -180,6 +186,7 @@ export default createESLintRule<Options, MessageIds>({
i18nDuplicateCustomId: `Duplicate custom ID "@@{{customId}}". See more at ${STYLE_GUIDE_LINK_UNIQUE_CUSTOM_IDS}`,
suggestAddI18nAttribute: 'Add the `i18n` attribute',
i18nMissingDescription: `Missing i18n description on element. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
i18nMissingMeaning: `Missing i18n meaning on element. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
},
},
defaultOptions: [DEFAULT_OPTIONS],
Expand All @@ -195,6 +202,7 @@ export default createESLintRule<Options, MessageIds>({
ignoreAttributes,
ignoreTags,
requireDescription,
requireMeaning,
},
],
) {
Expand All @@ -211,7 +219,7 @@ export default createESLintRule<Options, MessageIds>({
const collectedCustomIds = new Map<string, readonly ParseSourceSpan[]>();

function handleElement({
i18n: { description, customId },
i18n: { description, meaning, customId },
name,
parent,
sourceSpan,
Expand Down Expand Up @@ -240,6 +248,13 @@ export default createESLintRule<Options, MessageIds>({
loc,
});
}

if (requireMeaning && isEmpty(meaning)) {
context.report({
messageId: 'i18nMissingMeaning',
loc,
});
}
}

function handleTextAttribute({
Expand Down Expand Up @@ -353,12 +368,15 @@ export default createESLintRule<Options, MessageIds>({
}

return {
...((checkId || requireDescription) && {
...((checkId || requireDescription || requireMeaning) && {
'Element$1[i18n]'(node: StronglyTypedElement) {
handleElement(node);
},
}),
...((checkAttributes || checkId || requireDescription) && {
...((checkAttributes ||
checkId ||
requireDescription ||
requireMeaning) && {
[`Element$1 > TextAttribute[value=${PL_PATTERN}]`](
node: StronglyTypedTextAttribute,
) {
Expand Down

0 comments on commit 4ef0290

Please sign in to comment.