From fd5462fd4e33b12718ed0482fcc71d7b00993d69 Mon Sep 17 00:00:00 2001 From: James Nash Date: Wed, 4 Dec 2024 00:29:50 +0000 Subject: [PATCH] updated parseData() to reject top-level design token data --- .changeset/forty-boxes-shave.md | 5 +++ .changeset/strange-insects-cover.md | 5 +++ packages/parser-utils/README.md | 2 +- packages/parser-utils/src/parseData.test.ts | 40 ++++++++++----------- packages/parser-utils/src/parseData.ts | 31 ++++++++++++++-- 5 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 .changeset/forty-boxes-shave.md create mode 100644 .changeset/strange-insects-cover.md diff --git a/.changeset/forty-boxes-shave.md b/.changeset/forty-boxes-shave.md new file mode 100644 index 0000000..ca5ac45 --- /dev/null +++ b/.changeset/forty-boxes-shave.md @@ -0,0 +1,5 @@ +--- +"@udt/parser-utils": minor +--- + +BREAKING CHANGE: `parseData()` will now throw an `InvalidStructureError` if the top-level object in the input data is a design token. diff --git a/.changeset/strange-insects-cover.md b/.changeset/strange-insects-cover.md new file mode 100644 index 0000000..96922df --- /dev/null +++ b/.changeset/strange-insects-cover.md @@ -0,0 +1,5 @@ +--- +"@udt/parser-utils": minor +--- + +BREAKING CHANGE: Specified minimum Node version of 18 diff --git a/packages/parser-utils/README.md b/packages/parser-utils/README.md index 22cb35b..609e84b 100644 --- a/packages/parser-utils/README.md +++ b/packages/parser-utils/README.md @@ -16,7 +16,7 @@ const fileData = { /* ... */ }; // relevant properties of each group and design token // object it encounters to the functions you provide in // the config: -const parsedData = parseData(fileData, { +const parsedGroup = parseData(fileData, { /* Parser config */ // A function that checks whether an object is diff --git a/packages/parser-utils/src/parseData.test.ts b/packages/parser-utils/src/parseData.test.ts index aebbcaf..0d757ab 100644 --- a/packages/parser-utils/src/parseData.test.ts +++ b/packages/parser-utils/src/parseData.test.ts @@ -7,6 +7,7 @@ import { type ParserConfig, InvalidDataError, AddChildFn, + InvalidStructureError, } from "./parseData.js"; interface TestGroup { @@ -108,14 +109,14 @@ describe("parseData()", () => { }); describe("parsing an empty group object", () => { - let parsedGroupOrToken: TestGroup | TestDesignToken | undefined; + let parsedGroup: TestGroup | undefined; beforeEach(() => { - parsedGroupOrToken = parseData({}, parserConfig); + parsedGroup = parseData({}, parserConfig); }); it("returns a group", () => { - expect(parsedGroupOrToken?.type).toBe("group"); + expect(parsedGroup?.type).toBe("group"); }); it("calls isDesignTokenData function once", () => { @@ -141,33 +142,26 @@ describe("parseData()", () => { describe("parsing a design token object", () => { const testTokenData = { - value: "whatever", // <-- this makes it a token - other: "thing", - stuff: 123, - notAGroup: {}, + testToken: { + value: "whatever", // <-- this makes it a token + other: "thing", + stuff: 123, + notAGroup: {}, + }, }; - let parsedGroupOrToken: TestGroup | TestDesignToken | undefined; beforeEach(() => { - parsedGroupOrToken = parseData(testTokenData, parserConfig); - }); - - it("returns a design token", () => { - expect(parsedGroupOrToken?.type).toBe("token"); - }); - - it("does not call parseGroupData function", () => { - expect(mockParseGroupData).not.toHaveBeenCalled(); + parseData(testTokenData, parserConfig); }); it("calls parseDesignTokenData function once", () => { expect(mockParseDesignTokenData).toHaveBeenCalledOnce(); }); - it("calls parseDesignTokenData function with complete data and empty path array", () => { + it("calls parseDesignTokenData function with complete data and path array", () => { expect(mockParseDesignTokenData).toHaveBeenCalledWith( - testTokenData, - [], + testTokenData.testToken, + ["testToken"], undefined ); }); @@ -432,4 +426,10 @@ describe("parseData()", () => { expect((error as InvalidDataError).data).toBe(123); } }); + + it("throws an InvalidStructureError when there is no root group", () => { + expect(() => + parseData({ value: "naughty anonymous token" }, parserConfig) + ).toThrowError(InvalidStructureError); + }); }); diff --git a/packages/parser-utils/src/parseData.ts b/packages/parser-utils/src/parseData.ts index a2b2b46..d0aaa9a 100644 --- a/packages/parser-utils/src/parseData.ts +++ b/packages/parser-utils/src/parseData.ts @@ -173,6 +173,25 @@ export class InvalidDataError extends Error { } } +/** + * Thrown when the outermost object in the data passed to `parseData()` + * is not a group. + */ +export class InvalidStructureError extends Error { + /** + * The offending value. + */ + public data: unknown; + + constructor(data: unknown) { + super( + `Expected a group at the root level, but encountered a design token instead` + ); + this.name = "InvalidDataError"; + this.data = data; + } +} + /** * The internal data parsing implementation. * @@ -210,6 +229,10 @@ function parseDataImpl( let groupOrToken: ParsedGroup | ParsedDesignToken | undefined = undefined; if (isDesignTokenData(data)) { // looks like a token + if (path.length === 0) { + throw new InvalidStructureError(data); + } + groupOrToken = parseDesignTokenData(data, path, contextFromParent); if (addChildToGroup && path.length > 0 && parentGroup !== undefined) { addChildToGroup(parentGroup, path[path.length - 1], groupOrToken); @@ -275,6 +298,10 @@ export function parseData( data: unknown, config: ParserConfig, contextFromParent?: T -): ParsedDesignToken | ParsedGroup | undefined { - return parseDataImpl(data, config, contextFromParent); +): ParsedGroup | undefined { + // parseDataImpl() called with an empty path will never return + // a ParsedDesignToken + return parseDataImpl(data, config, contextFromParent) as + | ParsedGroup + | undefined; }