Skip to content

Commit 93e918e

Browse files
committed
refactor(@angular-devkit/build-angular): improve i18n project option validation
The `i18n` project field is used to configure i18n behavior for a project. The validation code has been reorganized to use two helper functions to centralize the type validation and exception throwing. This reduces the complexity of the analysis code as well as removing the need to rely on `@angular-devkit/core`.
1 parent 45964c7 commit 93e918e

File tree

1 file changed

+41
-31
lines changed

1 file changed

+41
-31
lines changed

packages/angular_devkit/build_angular/src/utils/i18n-options.ts

+41-31
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@
77
*/
88

99
import { BuilderContext } from '@angular-devkit/architect';
10-
import { json } from '@angular-devkit/core';
11-
import fs from 'fs';
12-
import module from 'module';
13-
import os from 'os';
14-
import path from 'path';
10+
import fs from 'node:fs';
11+
import { createRequire } from 'node:module';
12+
import os from 'node:os';
13+
import path from 'node:path';
1514
import { Schema as BrowserBuilderSchema, I18NTranslation } from '../builders/browser/schema';
1615
import { Schema as ServerBuilderSchema } from '../builders/server/schema';
1716
import { readTsconfig } from '../utils/read-tsconfig';
@@ -43,7 +42,7 @@ export interface I18nOptions {
4342
}
4443

4544
function normalizeTranslationFileOption(
46-
option: json.JsonValue,
45+
option: unknown,
4746
locale: string,
4847
expectObjectInError: boolean,
4948
): string[] {
@@ -65,14 +64,25 @@ function normalizeTranslationFileOption(
6564
throw new Error(errorMessage);
6665
}
6766

67+
function ensureObject(value: unknown, name: string): asserts value is Record<string, unknown> {
68+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
69+
throw new Error(`Project ${name} field is malformed. Expected an object.`);
70+
}
71+
}
72+
73+
function ensureString(value: unknown, name: string): asserts value is string {
74+
if (typeof value !== 'string') {
75+
throw new Error(`Project ${name} field is malformed. Expected a string.`);
76+
}
77+
}
78+
6879
export function createI18nOptions(
69-
metadata: json.JsonObject,
80+
projectMetadata: { i18n?: unknown },
7081
inline?: boolean | string[],
7182
): I18nOptions {
72-
if (metadata.i18n !== undefined && !json.isJsonObject(metadata.i18n)) {
73-
throw new Error('Project i18n field is malformed. Expected an object.');
74-
}
75-
metadata = metadata.i18n || {};
83+
const { i18n: metadata = {} } = projectMetadata;
84+
85+
ensureObject(metadata, 'i18n');
7686

7787
const i18n: I18nOptions = {
7888
inlineLocales: new Set<string>(),
@@ -86,24 +96,23 @@ export function createI18nOptions(
8696

8797
let rawSourceLocale;
8898
let rawSourceLocaleBaseHref;
89-
if (json.isJsonObject(metadata.sourceLocale)) {
90-
rawSourceLocale = metadata.sourceLocale.code;
91-
if (
92-
metadata.sourceLocale.baseHref !== undefined &&
93-
typeof metadata.sourceLocale.baseHref !== 'string'
94-
) {
95-
throw new Error('Project i18n sourceLocale baseHref field is malformed. Expected a string.');
96-
}
97-
rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
98-
} else {
99+
if (typeof metadata.sourceLocale === 'string') {
99100
rawSourceLocale = metadata.sourceLocale;
100-
}
101+
} else if (metadata.sourceLocale !== undefined) {
102+
ensureObject(metadata.sourceLocale, 'i18n sourceLocale');
101103

102-
if (rawSourceLocale !== undefined) {
103-
if (typeof rawSourceLocale !== 'string') {
104-
throw new Error('Project i18n sourceLocale field is malformed. Expected a string.');
104+
if (metadata.sourceLocale.code !== undefined) {
105+
ensureString(metadata.sourceLocale.code, 'i18n sourceLocale code');
106+
rawSourceLocale = metadata.sourceLocale.code;
107+
}
108+
109+
if (metadata.sourceLocale.baseHref !== undefined) {
110+
ensureString(metadata.sourceLocale.baseHref, 'i18n sourceLocale baseHref');
111+
rawSourceLocaleBaseHref = metadata.sourceLocale.baseHref;
105112
}
113+
}
106114

115+
if (rawSourceLocale !== undefined) {
107116
i18n.sourceLocale = rawSourceLocale;
108117
i18n.hasDefinedSourceLocale = true;
109118
}
@@ -113,16 +122,17 @@ export function createI18nOptions(
113122
baseHref: rawSourceLocaleBaseHref,
114123
};
115124

116-
if (metadata.locales !== undefined && !json.isJsonObject(metadata.locales)) {
117-
throw new Error('Project i18n locales field is malformed. Expected an object.');
118-
} else if (metadata.locales) {
125+
if (metadata.locales !== undefined) {
126+
ensureObject(metadata.locales, 'i18n locales');
127+
119128
for (const [locale, options] of Object.entries(metadata.locales)) {
120129
let translationFiles;
121130
let baseHref;
122-
if (json.isJsonObject(options)) {
131+
if (options && typeof options === 'object' && 'translation' in options) {
123132
translationFiles = normalizeTranslationFileOption(options.translation, locale, false);
124133

125-
if (typeof options.baseHref === 'string') {
134+
if ('baseHref' in options) {
135+
ensureString(options.baseHref, `i18n locales ${locale} baseHref`);
126136
baseHref = options.baseHref;
127137
}
128138
} else {
@@ -181,7 +191,7 @@ export async function configureI18nBuild<T extends BrowserBuilderSchema | Server
181191

182192
const projectRoot = path.join(context.workspaceRoot, (metadata.root as string) || '');
183193
// The trailing slash is required to signal that the path is a directory and not a file.
184-
const projectRequire = module.createRequire(projectRoot + '/');
194+
const projectRequire = createRequire(projectRoot + '/');
185195
const localeResolver = (locale: string) =>
186196
projectRequire.resolve(path.join(LOCALE_DATA_BASE_MODULE, locale));
187197

0 commit comments

Comments
 (0)