intor-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/package.json +73 -0
- package/src/build/build-schemas/build-schemas.ts +13 -0
- package/src/build/build-schemas/index.ts +1 -0
- package/src/build/build-types/build-types.ts +46 -0
- package/src/build/build-types/index.ts +1 -0
- package/src/build/build-types/output/append-config-block.ts +34 -0
- package/src/build/build-types/output/append-footer.ts +5 -0
- package/src/build/build-types/output/append-header.ts +11 -0
- package/src/build/build-types/output/index.ts +3 -0
- package/src/build/build-types/utils/indent.ts +3 -0
- package/src/build/build-types/utils/render-infer-node.ts +29 -0
- package/src/build/index.ts +3 -0
- package/src/build/types.ts +19 -0
- package/src/cli/commands/check.ts +50 -0
- package/src/cli/commands/generate.ts +61 -0
- package/src/cli/index.ts +24 -0
- package/src/core/collect-messages/collect-runtime-messages.ts +76 -0
- package/src/core/collect-messages/index.ts +1 -0
- package/src/core/collect-messages/readers.ts +23 -0
- package/src/core/collect-messages/resolve-messages-reader.ts +57 -0
- package/src/core/constants/extra-exts.ts +2 -0
- package/src/core/constants/generated-files.ts +3 -0
- package/src/core/constants/index.ts +7 -0
- package/src/core/diagnostics/collect.ts +59 -0
- package/src/core/diagnostics/group.ts +41 -0
- package/src/core/diagnostics/index.ts +2 -0
- package/src/core/diagnostics/messages.ts +51 -0
- package/src/core/diagnostics/rules/enforce-missing-replacements.ts +55 -0
- package/src/core/diagnostics/rules/enforce-missing-rich.ts +55 -0
- package/src/core/diagnostics/rules/key/empty.ts +34 -0
- package/src/core/diagnostics/rules/key/index.ts +2 -0
- package/src/core/diagnostics/rules/key/not-found.ts +43 -0
- package/src/core/diagnostics/rules/replacement/index.ts +3 -0
- package/src/core/diagnostics/rules/replacement/missing.ts +48 -0
- package/src/core/diagnostics/rules/replacement/not-allowed.ts +43 -0
- package/src/core/diagnostics/rules/replacement/unused.ts +48 -0
- package/src/core/diagnostics/rules/rich/index.ts +3 -0
- package/src/core/diagnostics/rules/rich/missing.ts +48 -0
- package/src/core/diagnostics/rules/rich/not-allowed.ts +43 -0
- package/src/core/diagnostics/rules/rich/unused.ts +48 -0
- package/src/core/diagnostics/types.ts +21 -0
- package/src/core/diagnostics/utils/get-schema-node-at-path.ts +29 -0
- package/src/core/diagnostics/utils/index-usages-by-key.ts +28 -0
- package/src/core/diagnostics/utils/resolve-key-path.ts +5 -0
- package/src/core/discover-configs/discover-configs.ts +87 -0
- package/src/core/discover-configs/index.ts +1 -0
- package/src/core/discover-configs/is-intor-resolved-config.ts +15 -0
- package/src/core/extract-usages/README.md +84 -0
- package/src/core/extract-usages/collectors/collect-key-usages.ts +40 -0
- package/src/core/extract-usages/collectors/collect-pre-keys.ts +58 -0
- package/src/core/extract-usages/collectors/collect-replacement-usages.ts +68 -0
- package/src/core/extract-usages/collectors/collect-rich-usages.ts +57 -0
- package/src/core/extract-usages/collectors/collect-translator-bindings.ts +56 -0
- package/src/core/extract-usages/collectors/index.ts +5 -0
- package/src/core/extract-usages/collectors/utils/extract-static-object-keys.ts +31 -0
- package/src/core/extract-usages/collectors/utils/get-config-key.ts +15 -0
- package/src/core/extract-usages/collectors/utils/get-object-arg.ts +42 -0
- package/src/core/extract-usages/collectors/utils/is-static-string-literal.ts +26 -0
- package/src/core/extract-usages/collectors/utils/walk-translator-bindings.ts +47 -0
- package/src/core/extract-usages/collectors/utils/walk-translator-method-calls.ts +42 -0
- package/src/core/extract-usages/extract-usages.ts +88 -0
- package/src/core/extract-usages/index.ts +13 -0
- package/src/core/extract-usages/load-source-files-from-tscofnig.ts +76 -0
- package/src/core/extract-usages/translator-registry.ts +20 -0
- package/src/core/extract-usages/types.ts +53 -0
- package/src/core/index.ts +13 -0
- package/src/core/infer-schema/index.ts +3 -0
- package/src/core/infer-schema/infer-schemas.ts +16 -0
- package/src/core/infer-schema/messages/index.ts +1 -0
- package/src/core/infer-schema/messages/infer-messages-schema.ts +64 -0
- package/src/core/infer-schema/replacements/extract-interpolation-names.ts +70 -0
- package/src/core/infer-schema/replacements/index.ts +1 -0
- package/src/core/infer-schema/replacements/infer-replacements-schema.ts +63 -0
- package/src/core/infer-schema/rich/index.ts +1 -0
- package/src/core/infer-schema/rich/infer-rich-schema.ts +66 -0
- package/src/core/infer-schema/types.ts +42 -0
- package/src/core/infer-schema/utils/infer-object.ts +32 -0
- package/src/core/infer-schema/utils/is-message-object.ts +5 -0
- package/src/core/read-generated-schema.ts +42 -0
- package/src/core/scan-logger.ts +10 -0
- package/src/core/write-generated-files.ts +51 -0
- package/src/features/check/check.ts +75 -0
- package/src/features/check/index.ts +1 -0
- package/src/features/check/print-summary.ts +28 -0
- package/src/features/generate/generate.ts +76 -0
- package/src/features/generate/index.ts +1 -0
- package/src/features/generate/print-configs.ts +8 -0
- package/src/features/generate/print-summary.ts +19 -0
- package/src/features/index.ts +2 -0
- package/src/features/print-title.ts +7 -0
- package/src/features/spinner.ts +3 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Diagnostic } from "./types";
|
|
2
|
+
import type { ExtractedUsages } from "../extract-usages";
|
|
3
|
+
import type { InferredSchemas } from "../infer-schema/types";
|
|
4
|
+
import { enforceMissingReplacements } from "./rules/enforce-missing-replacements";
|
|
5
|
+
import { enforceMissingRich } from "./rules/enforce-missing-rich";
|
|
6
|
+
import { keyEmpty, keyNotFound } from "./rules/key";
|
|
7
|
+
import {
|
|
8
|
+
replacementsNotAllowed,
|
|
9
|
+
replacementsMissing,
|
|
10
|
+
replacementsUnused,
|
|
11
|
+
} from "./rules/replacement";
|
|
12
|
+
import { richMissing, richNotAllowed, richUnused } from "./rules/rich";
|
|
13
|
+
import { indexUsagesByKey } from "./utils/index-usages-by-key";
|
|
14
|
+
|
|
15
|
+
export function collectDiagnostics(
|
|
16
|
+
{ messagesSchema, replacementsSchema, richSchema }: InferredSchemas,
|
|
17
|
+
usages: ExtractedUsages,
|
|
18
|
+
) {
|
|
19
|
+
const diagnostics: Diagnostic[] = [];
|
|
20
|
+
|
|
21
|
+
// Key
|
|
22
|
+
for (const usage of usages.keys) {
|
|
23
|
+
diagnostics.push(...keyNotFound(usage, messagesSchema), ...keyEmpty(usage));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Replacements
|
|
27
|
+
for (const usage of usages.replacements) {
|
|
28
|
+
diagnostics.push(
|
|
29
|
+
...replacementsNotAllowed(usage, replacementsSchema),
|
|
30
|
+
...replacementsMissing(usage, replacementsSchema),
|
|
31
|
+
...replacementsUnused(usage, replacementsSchema),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Rich
|
|
36
|
+
for (const usage of usages.rich) {
|
|
37
|
+
diagnostics.push(
|
|
38
|
+
...richNotAllowed(usage, richSchema),
|
|
39
|
+
...richMissing(usage, richSchema),
|
|
40
|
+
...richUnused(usage, richSchema),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Ensure required replacements / rich tags are detected even when no usage provides them
|
|
45
|
+
const replacementIndex = indexUsagesByKey(usages.replacements);
|
|
46
|
+
const richIndex = indexUsagesByKey(usages.rich);
|
|
47
|
+
for (const usage of usages.keys) {
|
|
48
|
+
diagnostics.push(
|
|
49
|
+
...enforceMissingReplacements(
|
|
50
|
+
usage,
|
|
51
|
+
replacementIndex,
|
|
52
|
+
replacementsSchema,
|
|
53
|
+
),
|
|
54
|
+
...enforceMissingRich(usage, richIndex, richSchema),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return diagnostics;
|
|
59
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/* eslint-disable unicorn/no-array-sort */
|
|
2
|
+
import type { Diagnostic, DiagnosticGroup } from "./types";
|
|
3
|
+
|
|
4
|
+
export function groupDiagnostics(diagnostics: Diagnostic[]): DiagnosticGroup[] {
|
|
5
|
+
const map = new Map<string, DiagnosticGroup>();
|
|
6
|
+
|
|
7
|
+
for (const diagnostic of diagnostics) {
|
|
8
|
+
const { severity, method, message, messageKey, file, line, column } =
|
|
9
|
+
diagnostic;
|
|
10
|
+
|
|
11
|
+
const groupId = messageKey
|
|
12
|
+
? `${file}::${messageKey}::${diagnostic.method}`
|
|
13
|
+
: `${file}::${line}:${column}`;
|
|
14
|
+
|
|
15
|
+
if (!map.has(groupId)) {
|
|
16
|
+
map.set(groupId, {
|
|
17
|
+
severity,
|
|
18
|
+
method,
|
|
19
|
+
messageKey,
|
|
20
|
+
problems: [],
|
|
21
|
+
file,
|
|
22
|
+
lines: [],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const group = map.get(groupId)!;
|
|
26
|
+
|
|
27
|
+
group.problems.push(message);
|
|
28
|
+
group.lines.push(line);
|
|
29
|
+
|
|
30
|
+
// severity upgrade (error > warn)
|
|
31
|
+
if (severity === "error") {
|
|
32
|
+
group.severity = "error";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const group of map.values()) {
|
|
37
|
+
group.lines = [...new Set(group.lines)].sort((a, b) => a - b);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...map.values()];
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const DIAGNOSTIC_MESSAGES = {
|
|
2
|
+
// --------------------------------------------------
|
|
3
|
+
// Message key
|
|
4
|
+
// --------------------------------------------------
|
|
5
|
+
KEY_NOT_FOUND: {
|
|
6
|
+
code: "INTOR_KEY_NOT_FOUND",
|
|
7
|
+
message: () => "does not exist",
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
KEY_EMPTY: {
|
|
11
|
+
code: "INTOR_KEY_EMPTY",
|
|
12
|
+
message: () => "translation key cannot be empty",
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// --------------------------------------------------
|
|
16
|
+
// Replacements
|
|
17
|
+
// --------------------------------------------------
|
|
18
|
+
REPLACEMENTS_NOT_ALLOWED: {
|
|
19
|
+
code: "INTOR_REPLACEMENTS_NOT_ALLOWED",
|
|
20
|
+
message: () => "does not accept replacements",
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
REPLACEMENTS_MISSING: {
|
|
24
|
+
code: "INTOR_REPLACEMENTS_MISSING",
|
|
25
|
+
message: (missing: string[]) =>
|
|
26
|
+
`missing replacements: ${missing.join(", ")}`,
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
REPLACEMENTS_UNUSED: {
|
|
30
|
+
code: "INTOR_REPLACEMENTS_UNUSED",
|
|
31
|
+
message: (extra: string[]) => `unused replacements: ${extra.join(", ")}`,
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// --------------------------------------------------
|
|
35
|
+
// Rich tags
|
|
36
|
+
// --------------------------------------------------
|
|
37
|
+
RICH_NOT_ALLOWED: {
|
|
38
|
+
code: "INTOR_RICH_NOT_ALLOWED",
|
|
39
|
+
message: () => "does not accept rich tags",
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
RICH_MISSING: {
|
|
43
|
+
code: "INTOR_RICH_MISSING",
|
|
44
|
+
message: (missing: string[]) => `missing rich tags: ${missing.join(", ")}`,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
RICH_UNUSED: {
|
|
48
|
+
code: "INTOR_RICH_UNUSED",
|
|
49
|
+
message: (extra: string[]) => `unused rich tags: ${extra.join(", ")}`,
|
|
50
|
+
},
|
|
51
|
+
} as const;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { KeyUsage, ReplacementUsage } from "../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect missing replacements when no replacement usage exists anywhere.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Expected:
|
|
14
|
+
* t("hello", { name })
|
|
15
|
+
*
|
|
16
|
+
* // Received:
|
|
17
|
+
* t("hello") // Replacement usage cannot be detected
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function enforceMissingReplacements(
|
|
21
|
+
usage: KeyUsage,
|
|
22
|
+
replacementIndex: Map<string, ReplacementUsage[]>,
|
|
23
|
+
replacementsSchema: InferNode,
|
|
24
|
+
): Diagnostic[] {
|
|
25
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
26
|
+
const diagnostics: Diagnostic[] = [];
|
|
27
|
+
|
|
28
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
29
|
+
|
|
30
|
+
// Replacements provided elsewhere
|
|
31
|
+
if (replacementIndex.has(`${usage.method}::${keyPath}`)) return diagnostics;
|
|
32
|
+
|
|
33
|
+
const schemaNode = getSchemaNodeAtPath(replacementsSchema, keyPath);
|
|
34
|
+
|
|
35
|
+
// No replacement schema defined
|
|
36
|
+
if (!schemaNode || schemaNode.kind !== "object") return diagnostics;
|
|
37
|
+
|
|
38
|
+
const expected: string[] = Object.keys(schemaNode.properties);
|
|
39
|
+
|
|
40
|
+
// No required replacements
|
|
41
|
+
if (expected.length === 0) return diagnostics;
|
|
42
|
+
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
severity: "warn",
|
|
45
|
+
method,
|
|
46
|
+
messageKey: keyPath,
|
|
47
|
+
code: DIAGNOSTIC_MESSAGES.REPLACEMENTS_MISSING.code,
|
|
48
|
+
message: DIAGNOSTIC_MESSAGES.REPLACEMENTS_MISSING.message(expected),
|
|
49
|
+
file,
|
|
50
|
+
line,
|
|
51
|
+
column,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return diagnostics;
|
|
55
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { KeyUsage, RichUsage } from "../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect missing rich when no rich usage exists anywhere.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* // Expected:
|
|
14
|
+
* tRich("hello", { link })
|
|
15
|
+
*
|
|
16
|
+
* // Received:
|
|
17
|
+
* tRich("hello") // Rich usage cannot be detected
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function enforceMissingRich(
|
|
21
|
+
usage: KeyUsage,
|
|
22
|
+
richIndex: Map<string, RichUsage[]>,
|
|
23
|
+
richSchema: InferNode,
|
|
24
|
+
): Diagnostic[] {
|
|
25
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
26
|
+
const diagnostics: Diagnostic[] = [];
|
|
27
|
+
|
|
28
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
29
|
+
|
|
30
|
+
// Rich tags provided elsewhere
|
|
31
|
+
if (richIndex.has(`${method}::${keyPath}`)) return diagnostics;
|
|
32
|
+
|
|
33
|
+
const schemaNode = getSchemaNodeAtPath(richSchema, keyPath);
|
|
34
|
+
|
|
35
|
+
// No rich schema defined
|
|
36
|
+
if (!schemaNode || schemaNode.kind !== "object") return diagnostics;
|
|
37
|
+
|
|
38
|
+
const expected: string[] = Object.keys(schemaNode.properties);
|
|
39
|
+
|
|
40
|
+
// No required rich tags
|
|
41
|
+
if (expected.length === 0) return diagnostics;
|
|
42
|
+
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
severity: "warn",
|
|
45
|
+
method,
|
|
46
|
+
messageKey: keyPath,
|
|
47
|
+
code: DIAGNOSTIC_MESSAGES.RICH_MISSING.code,
|
|
48
|
+
message: DIAGNOSTIC_MESSAGES.RICH_MISSING.message(expected),
|
|
49
|
+
file,
|
|
50
|
+
line,
|
|
51
|
+
column,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return diagnostics;
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { KeyUsage } from "../../../extract-usages";
|
|
2
|
+
import type { Diagnostic } from "../../types";
|
|
3
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // Expected:
|
|
9
|
+
* t("hello")
|
|
10
|
+
*
|
|
11
|
+
* // Received:
|
|
12
|
+
* t("")
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function keyEmpty(usage: KeyUsage): Diagnostic[] {
|
|
16
|
+
const { method, key, file, line, column } = usage;
|
|
17
|
+
|
|
18
|
+
if (!key) {
|
|
19
|
+
return [
|
|
20
|
+
{
|
|
21
|
+
severity: "warn",
|
|
22
|
+
method,
|
|
23
|
+
messageKey: key,
|
|
24
|
+
code: DIAGNOSTIC_MESSAGES.KEY_EMPTY.code,
|
|
25
|
+
message: DIAGNOSTIC_MESSAGES.KEY_EMPTY.message(),
|
|
26
|
+
file,
|
|
27
|
+
line,
|
|
28
|
+
column,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { KeyUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* t("hello")
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* t("missing")
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function keyNotFound(
|
|
19
|
+
usage: KeyUsage,
|
|
20
|
+
messagesSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
if (!key) return [];
|
|
25
|
+
|
|
26
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
27
|
+
if (!getSchemaNodeAtPath(messagesSchema, keyPath)) {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
severity: "warn",
|
|
31
|
+
method,
|
|
32
|
+
messageKey: key,
|
|
33
|
+
code: DIAGNOSTIC_MESSAGES.KEY_NOT_FOUND.code,
|
|
34
|
+
message: DIAGNOSTIC_MESSAGES.KEY_NOT_FOUND.message(),
|
|
35
|
+
file,
|
|
36
|
+
line,
|
|
37
|
+
column,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReplacementUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* t("hello", { name, phone })
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* t("hello", { name })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function replacementsMissing(
|
|
19
|
+
usage: ReplacementUsage,
|
|
20
|
+
replacementsSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(replacementsSchema, keyPath);
|
|
26
|
+
if (!schemaNode || schemaNode.kind !== "object") return [];
|
|
27
|
+
|
|
28
|
+
const expected = Object.keys(schemaNode.properties);
|
|
29
|
+
const actual = usage.replacements;
|
|
30
|
+
const missing = expected.filter((name) => !actual.includes(name));
|
|
31
|
+
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
severity: "warn",
|
|
36
|
+
method,
|
|
37
|
+
messageKey: keyPath,
|
|
38
|
+
code: DIAGNOSTIC_MESSAGES.REPLACEMENTS_MISSING.code,
|
|
39
|
+
message: DIAGNOSTIC_MESSAGES.REPLACEMENTS_MISSING.message(missing),
|
|
40
|
+
file,
|
|
41
|
+
line,
|
|
42
|
+
column,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReplacementUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* t("hello")
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* t("hello", { name })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function replacementsNotAllowed(
|
|
19
|
+
usage: ReplacementUsage,
|
|
20
|
+
replacementsSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(replacementsSchema, keyPath);
|
|
26
|
+
|
|
27
|
+
if (!schemaNode || schemaNode.kind !== "object") {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
severity: "warn",
|
|
31
|
+
method,
|
|
32
|
+
messageKey: keyPath,
|
|
33
|
+
code: DIAGNOSTIC_MESSAGES.REPLACEMENTS_NOT_ALLOWED.code,
|
|
34
|
+
message: DIAGNOSTIC_MESSAGES.REPLACEMENTS_NOT_ALLOWED.message(),
|
|
35
|
+
file,
|
|
36
|
+
line,
|
|
37
|
+
column,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReplacementUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* t("hello", { name })
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* t("hello", { name, phone })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function replacementsUnused(
|
|
19
|
+
usage: ReplacementUsage,
|
|
20
|
+
replacementsSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(replacementsSchema, keyPath);
|
|
26
|
+
if (!schemaNode || schemaNode.kind !== "object") return [];
|
|
27
|
+
|
|
28
|
+
const expected = Object.keys(schemaNode.properties);
|
|
29
|
+
const actual = usage.replacements;
|
|
30
|
+
const extra = actual.filter((name) => !expected.includes(name));
|
|
31
|
+
|
|
32
|
+
if (extra.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
severity: "warn",
|
|
36
|
+
method,
|
|
37
|
+
messageKey: keyPath,
|
|
38
|
+
code: DIAGNOSTIC_MESSAGES.REPLACEMENTS_UNUSED.code,
|
|
39
|
+
message: DIAGNOSTIC_MESSAGES.REPLACEMENTS_UNUSED.message(extra),
|
|
40
|
+
file,
|
|
41
|
+
line,
|
|
42
|
+
column,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RichUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* tRich("hello", { link })
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* tRich("hello", { link, b })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function richMissing(
|
|
19
|
+
usage: RichUsage,
|
|
20
|
+
richSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(richSchema, keyPath);
|
|
26
|
+
if (!schemaNode || schemaNode.kind !== "object") return [];
|
|
27
|
+
|
|
28
|
+
const expected = Object.keys(schemaNode.properties);
|
|
29
|
+
const actual = usage.rich;
|
|
30
|
+
const missing = expected.filter((tag) => !actual.includes(tag));
|
|
31
|
+
|
|
32
|
+
if (missing.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
severity: "warn",
|
|
36
|
+
method,
|
|
37
|
+
messageKey: keyPath,
|
|
38
|
+
code: DIAGNOSTIC_MESSAGES.RICH_MISSING.code,
|
|
39
|
+
message: DIAGNOSTIC_MESSAGES.RICH_MISSING.message(missing),
|
|
40
|
+
file,
|
|
41
|
+
line,
|
|
42
|
+
column,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { RichUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* tRich("hello")
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* tRich("hello", { link })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function richNotAllowed(
|
|
19
|
+
usage: RichUsage,
|
|
20
|
+
richSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(richSchema, keyPath);
|
|
26
|
+
|
|
27
|
+
if (!schemaNode || schemaNode.kind !== "object") {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
severity: "warn",
|
|
31
|
+
method,
|
|
32
|
+
messageKey: keyPath,
|
|
33
|
+
code: DIAGNOSTIC_MESSAGES.RICH_NOT_ALLOWED.code,
|
|
34
|
+
message: DIAGNOSTIC_MESSAGES.RICH_NOT_ALLOWED.message(),
|
|
35
|
+
file,
|
|
36
|
+
line,
|
|
37
|
+
column,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { RichUsage } from "../../../extract-usages";
|
|
2
|
+
import type { InferNode } from "../../../infer-schema";
|
|
3
|
+
import type { Diagnostic } from "../../types";
|
|
4
|
+
import { DIAGNOSTIC_MESSAGES } from "../../messages";
|
|
5
|
+
import { getSchemaNodeAtPath } from "../../utils/get-schema-node-at-path";
|
|
6
|
+
import { resolveKeyPath } from "../../utils/resolve-key-path";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // Expected:
|
|
12
|
+
* t("hello", { name })
|
|
13
|
+
*
|
|
14
|
+
* // Received:
|
|
15
|
+
* t("hello", { name, phone })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function richUnused(
|
|
19
|
+
usage: RichUsage,
|
|
20
|
+
richSchema: InferNode,
|
|
21
|
+
): Diagnostic[] {
|
|
22
|
+
const { method, key, preKey, file, line, column } = usage;
|
|
23
|
+
|
|
24
|
+
const keyPath = resolveKeyPath(key, preKey);
|
|
25
|
+
const schemaNode = getSchemaNodeAtPath(richSchema, keyPath);
|
|
26
|
+
if (!schemaNode || schemaNode.kind !== "object") return [];
|
|
27
|
+
|
|
28
|
+
const expected = Object.keys(schemaNode.properties);
|
|
29
|
+
const actual = usage.rich;
|
|
30
|
+
const extra = actual.filter((tag) => !expected.includes(tag));
|
|
31
|
+
|
|
32
|
+
if (extra.length > 0) {
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
severity: "warn",
|
|
36
|
+
method,
|
|
37
|
+
messageKey: keyPath,
|
|
38
|
+
code: DIAGNOSTIC_MESSAGES.RICH_UNUSED.code,
|
|
39
|
+
message: DIAGNOSTIC_MESSAGES.RICH_UNUSED.message(extra),
|
|
40
|
+
file,
|
|
41
|
+
line,
|
|
42
|
+
column,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TranslatorMethod } from "../extract-usages";
|
|
2
|
+
|
|
3
|
+
export interface Diagnostic {
|
|
4
|
+
severity: "error" | "warn";
|
|
5
|
+
method: TranslatorMethod;
|
|
6
|
+
messageKey: string;
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DiagnosticGroup {
|
|
15
|
+
severity: "error" | "warn";
|
|
16
|
+
method: TranslatorMethod;
|
|
17
|
+
messageKey: string;
|
|
18
|
+
problems: string[]; // list of bullet messages
|
|
19
|
+
file: string;
|
|
20
|
+
lines: number[]; // sorted, unique
|
|
21
|
+
}
|