Skip to content

Commit

Permalink
feat: add no-invalid-variables rule
Browse files Browse the repository at this point in the history
  • Loading branch information
radiovisual committed Jun 1, 2024
1 parent 0ff0474 commit 1a7824a
Show file tree
Hide file tree
Showing 15 changed files with 735 additions and 13 deletions.
3 changes: 2 additions & 1 deletion jest.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"extensionsToTreatAsEsm": [".ts"],
"preset": "ts-jest",
"testEnvironment": "node"
"testEnvironment": "node",
"testPathIgnorePatterns": ["dist"]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"typescript": "^5.4.5"
},
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.7.8",
"chalk": "^4.0.0"
}
}
12 changes: 6 additions & 6 deletions src/classes/problem.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class Problem {
severity: RuleSeverity;
locale: string;
message: string;
expected?: string;
received?: string;
expected?: string | object | number;
received?: string | object | number;

public constructor(builder: ProblemBuilder) {
this.ruleMeta = builder.ruleMeta;
Expand All @@ -29,8 +29,8 @@ class ProblemBuilder {
locale!: string;
url!: string;
message!: string;
expected?: string;
received?: string;
expected?: string | object | number;
received?: string | object | number;

withSeverity(severity: RuleSeverity): this {
this.severity = severity;
Expand All @@ -52,12 +52,12 @@ class ProblemBuilder {
return this;
}

withExpected(expected?: string): this {
withExpected(expected?: string | object | number): this {
this.expected = expected;
return this;
}

withReceived(received?: string): this {
withReceived(received?: string | object | number): this {
this.received = received;
return this;
}
Expand Down
3 changes: 2 additions & 1 deletion src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { noEmptyMessages } from "./no-empty-messages/no-empty-messages.js";
import { noUntranslatedMessages } from "./no-untranslated-messages/index.js";
import { noInvalidVariables } from "./no-invalid-variables/index.js";

export { noUntranslatedMessages, noEmptyMessages };
export { noUntranslatedMessages, noEmptyMessages, noInvalidVariables };
95 changes: 95 additions & 0 deletions src/rules/no-invalid-variables/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# no-invalid-variables

## Rule Details

All variables declared in the base messages file and any translated files must have valid syntax, and the variables declared in the base (source) messages file must exist in the translated string.

Additionally, if there are variables declared in the translated files, those same variables must exist in the source string.

### Key considerations

1. Whitespace is ignored in the comparison, so these two variables would be seen as the same: `{foo}` and `{ foo }`
2. Variable comparisions are case-sensitive, so each of these variables will be seen as a different variable: `{foo}`, `{Foo}`, `{FOo}` and `{FOO}`
3. Strings with rouge brackets will trigger. For example, consider this string: `"Here is an opening bracket: {"`. The rule logic will see the opening bracket in the string and think there is a malformed variable syntax found. If you get these type of false positives, then you could consider using the `ignoreKeys` override in the configuation.

Assuming `en.json` is where your default language is (the source file):

❌ Example of **incorrect** setup for this rule (the `firstName` variable is missing or malformed):

```js
en.json: { 'hello': 'Hi, {firstName}' }
fr.json: { 'hello': 'Salut, {}' }
de.json: { 'hello': 'Hallo, firstName}' }
```

❌ Example of **incorrect** setup for this rule (the `firstName` variable has different casing):

```js
en.json: { 'hello': 'Hi, {firstName}' }
fr.json: { 'hello': 'Salut, {firstNAME}' }
de.json: { 'hello': 'Hallo, {FIRSTName}' }
```

❌ Example of **incorrect** setup for this rule (the `firstName` variable has been accidently translated):

```js
en.json: { 'hello': 'Hi, {firstName}' }
fr.json: { 'hello': 'Salut, {préNom}' }
de.json: { 'hello': 'Hallo, {vorName}' }
```

❌ Example of **incorrect** setup for this rule (mismatched brackets):

```js
en.json: { 'hello': 'Hi, {' }
fr.json: { 'hello': 'Salut, }' }
de.json: { 'hello': 'Hallo, {{}' }
```

❌ Example of **incorrect** setup for this rule (variables in the translated files do not exist in the source message):

```js
en.json: { 'hello': 'Hi' }
fr.json: { 'hello': 'Salut, {firstName}' }
de.json: { 'hello': 'Hallo, {firstName}' }
```

✅ Examples of a **correct** setup for this rule (all variables are the same):

```js
en.json: { 'hello': 'Hi, {firstName}' }
fr.json: { 'hello': 'Salut, {firstName}' }
de.json: { 'hello': 'Hallo, {firstName}' }
```

## Example Configuration

Simple configuration where you just supply the severity level:

```json
{
"rules": {
"no-invalid-variables": "error"
}
}
```

Advanced configuration where you can pass extra configuration to the rule:

```json
{
"rules": {
"no-invalid-variables": {
"severity": "error",
"ignoreKeys": ["foo", "bar"]
}
}
}
```

> [!IMPORTANT]
> Be careful when using the `ignoreKeys` array: ignoring keys means means potentially ignoring real problems that can affect the UI/UX and reliability of your application.
## Version

This rule was introduced in i18n-validator v1.0.0.
1 change: 1 addition & 0 deletions src/rules/no-invalid-variables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { noInvalidVariables } from "./no-invalid-variables";
Loading

0 comments on commit 1a7824a

Please sign in to comment.