Skip to content

Commit fd5462f

Browse files
committed
updated parseData() to reject top-level design token data
1 parent 0ff5419 commit fd5462f

File tree

5 files changed

+60
-23
lines changed

5 files changed

+60
-23
lines changed

.changeset/forty-boxes-shave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@udt/parser-utils": minor
3+
---
4+
5+
BREAKING CHANGE: `parseData()` will now throw an `InvalidStructureError` if the top-level object in the input data is a design token.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@udt/parser-utils": minor
3+
---
4+
5+
BREAKING CHANGE: Specified minimum Node version of 18

packages/parser-utils/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const fileData = { /* ... */ };
1616
// relevant properties of each group and design token
1717
// object it encounters to the functions you provide in
1818
// the config:
19-
const parsedData = parseData(fileData, {
19+
const parsedGroup = parseData(fileData, {
2020
/* Parser config */
2121

2222
// A function that checks whether an object is

packages/parser-utils/src/parseData.test.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type ParserConfig,
88
InvalidDataError,
99
AddChildFn,
10+
InvalidStructureError,
1011
} from "./parseData.js";
1112

1213
interface TestGroup {
@@ -108,14 +109,14 @@ describe("parseData()", () => {
108109
});
109110

110111
describe("parsing an empty group object", () => {
111-
let parsedGroupOrToken: TestGroup | TestDesignToken | undefined;
112+
let parsedGroup: TestGroup | undefined;
112113

113114
beforeEach(() => {
114-
parsedGroupOrToken = parseData({}, parserConfig);
115+
parsedGroup = parseData({}, parserConfig);
115116
});
116117

117118
it("returns a group", () => {
118-
expect(parsedGroupOrToken?.type).toBe("group");
119+
expect(parsedGroup?.type).toBe("group");
119120
});
120121

121122
it("calls isDesignTokenData function once", () => {
@@ -141,33 +142,26 @@ describe("parseData()", () => {
141142

142143
describe("parsing a design token object", () => {
143144
const testTokenData = {
144-
value: "whatever", // <-- this makes it a token
145-
other: "thing",
146-
stuff: 123,
147-
notAGroup: {},
145+
testToken: {
146+
value: "whatever", // <-- this makes it a token
147+
other: "thing",
148+
stuff: 123,
149+
notAGroup: {},
150+
},
148151
};
149-
let parsedGroupOrToken: TestGroup | TestDesignToken | undefined;
150152

151153
beforeEach(() => {
152-
parsedGroupOrToken = parseData(testTokenData, parserConfig);
153-
});
154-
155-
it("returns a design token", () => {
156-
expect(parsedGroupOrToken?.type).toBe("token");
157-
});
158-
159-
it("does not call parseGroupData function", () => {
160-
expect(mockParseGroupData).not.toHaveBeenCalled();
154+
parseData(testTokenData, parserConfig);
161155
});
162156

163157
it("calls parseDesignTokenData function once", () => {
164158
expect(mockParseDesignTokenData).toHaveBeenCalledOnce();
165159
});
166160

167-
it("calls parseDesignTokenData function with complete data and empty path array", () => {
161+
it("calls parseDesignTokenData function with complete data and path array", () => {
168162
expect(mockParseDesignTokenData).toHaveBeenCalledWith(
169-
testTokenData,
170-
[],
163+
testTokenData.testToken,
164+
["testToken"],
171165
undefined
172166
);
173167
});
@@ -432,4 +426,10 @@ describe("parseData()", () => {
432426
expect((error as InvalidDataError).data).toBe(123);
433427
}
434428
});
429+
430+
it("throws an InvalidStructureError when there is no root group", () => {
431+
expect(() =>
432+
parseData({ value: "naughty anonymous token" }, parserConfig)
433+
).toThrowError(InvalidStructureError);
434+
});
435435
});

packages/parser-utils/src/parseData.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,25 @@ export class InvalidDataError extends Error {
173173
}
174174
}
175175

176+
/**
177+
* Thrown when the outermost object in the data passed to `parseData()`
178+
* is not a group.
179+
*/
180+
export class InvalidStructureError extends Error {
181+
/**
182+
* The offending value.
183+
*/
184+
public data: unknown;
185+
186+
constructor(data: unknown) {
187+
super(
188+
`Expected a group at the root level, but encountered a design token instead`
189+
);
190+
this.name = "InvalidDataError";
191+
this.data = data;
192+
}
193+
}
194+
176195
/**
177196
* The internal data parsing implementation.
178197
*
@@ -210,6 +229,10 @@ function parseDataImpl<ParsedDesignToken, ParsedGroup, T>(
210229
let groupOrToken: ParsedGroup | ParsedDesignToken | undefined = undefined;
211230
if (isDesignTokenData(data)) {
212231
// looks like a token
232+
if (path.length === 0) {
233+
throw new InvalidStructureError(data);
234+
}
235+
213236
groupOrToken = parseDesignTokenData(data, path, contextFromParent);
214237
if (addChildToGroup && path.length > 0 && parentGroup !== undefined) {
215238
addChildToGroup(parentGroup, path[path.length - 1], groupOrToken);
@@ -275,6 +298,10 @@ export function parseData<ParsedDesignToken, ParsedGroup, T>(
275298
data: unknown,
276299
config: ParserConfig<ParsedDesignToken, ParsedGroup, T>,
277300
contextFromParent?: T
278-
): ParsedDesignToken | ParsedGroup | undefined {
279-
return parseDataImpl(data, config, contextFromParent);
301+
): ParsedGroup | undefined {
302+
// parseDataImpl() called with an empty path will never return
303+
// a ParsedDesignToken
304+
return parseDataImpl(data, config, contextFromParent) as
305+
| ParsedGroup
306+
| undefined;
280307
}

0 commit comments

Comments
 (0)