Skip to content

Commit 0bff987

Browse files
committed
fix: #1609 resolve .length on array interpolation params
Widen the isArray branch of getValue() to short-circuit on a terminal "length" key, and gate numeric-index lookups behind a /^\d+$/ regex so malformed keys like "1x", "1.5", and "-1" are rejected (parseInt would otherwise parse leading digits and silently accept them). Adds unit tests pinning the contract (length works, prototype props like constructor/toString/push remain inaccessible, length.foo is terminal) plus a parser-level regression guard for the original {{array.length}} interpolation case. Fixes #1609
1 parent 5bc8773 commit 0bff987

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

projects/ngx-translate/src/lib/util.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,22 @@ export function getValue(target: unknown, key: string): unknown {
154154
}
155155

156156
if (isArray(target)) {
157-
const index = parseInt(key, 10);
158-
if (
159-
isDefined(target[index]) &&
160-
(isDict(target[index]) || isArray(target[index]) || isLastKey)
161-
) {
162-
target = target[index];
157+
if (key === "length" && isLastKey) {
158+
target = target.length;
163159
key = "";
164160
continue;
165161
}
162+
if (/^\d+$/.test(key)) {
163+
const index = parseInt(key, 10);
164+
if (
165+
isDefined(target[index]) &&
166+
(isDict(target[index]) || isArray(target[index]) || isLastKey)
167+
) {
168+
target = target[index];
169+
key = "";
170+
continue;
171+
}
172+
}
166173
}
167174
}
168175

projects/ngx-translate/src/tests/translate.parser.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,20 @@ describe("Parser", () => {
3535
).toEqual("This is a value3");
3636
});
3737

38+
it("should interpolate an array's length (regression #1609)", () => {
39+
expect(
40+
parser.interpolate("Found {{matches.length}} items", {
41+
matches: ["a", "b", "c"],
42+
}),
43+
).toEqual("Found 3 items");
44+
45+
expect(
46+
parser.interpolate("You have {{items.length}} messages", {
47+
items: [],
48+
}),
49+
).toEqual("You have 0 messages");
50+
});
51+
3852
it("should support interpolation functions", () => {
3953
const uc: InterpolateFunction = (params) => {
4054
return (getValue(params, "x") as string)?.toUpperCase() + " YOU!";

projects/ngx-translate/src/tests/utils.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,24 @@ describe("Utils", () => {
226226
expect(getValue(["A", ["a", "b", "c"], "C"], "1.2")).toEqual("c");
227227

228228
expect(getValue("test", "key")).not.toBeDefined();
229+
230+
// length property on arrays — regression guard for #1609
231+
expect(getValue([1, 2, 3], "length")).toEqual(3);
232+
expect(getValue([], "length")).toEqual(0);
233+
expect(getValue({ matches: ["a", "b", "c"] }, "matches.length")).toEqual(3);
234+
235+
// malformed numeric keys are rejected (parseInt-leading-digits loophole)
236+
expect(getValue([1, 2, 3], "1x")).not.toBeDefined();
237+
expect(getValue([1, 2, 3], "1.5")).not.toBeDefined();
238+
expect(getValue([1, 2, 3], "-1")).not.toBeDefined();
239+
240+
// tight contract: other array properties are NOT exposed via getValue
241+
expect(getValue([1, 2, 3], "constructor")).not.toBeDefined();
242+
expect(getValue([1, 2, 3], "toString")).not.toBeDefined();
243+
expect(getValue([1, 2, 3], "push")).not.toBeDefined();
244+
245+
// length is terminal — chaining past it returns undefined
246+
expect(getValue([1, 2, 3], "length.foo")).not.toBeDefined();
229247
});
230248
});
231249

0 commit comments

Comments
 (0)