Skip to content

Commit

Permalink
updated parser-utils API
Browse files Browse the repository at this point in the history
  • Loading branch information
c1rrus committed Nov 28, 2024
1 parent 6e8be7b commit 4076c0e
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 106 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-dragons-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@udt/parser-utils": minor
---

BREAKING CHANGE: `ParseGroupDataFn` can no longer return an `addChild` function. Instead, you should now provide an `addChildToGroup` function in your parser config.
5 changes: 5 additions & 0 deletions .changeset/slow-insects-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@udt/parser-utils": minor
---

BREAKING CHANGE: `parseData()` may now return `undefined` (happens if no `parseGroupData` function is set in the config).
5 changes: 5 additions & 0 deletions .changeset/tidy-islands-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@udt/parser-utils": minor
---

BREAKING CHANGE: `extractProperties()` now returns an object containing unextract properties and their values, instead of an array of unextracted property names.
1 change: 0 additions & 1 deletion packages/demos/src/simple-dtcg-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export function parseDtcg(data: unknown) {
}

return {
group: undefined,
contextForChildren,
};
}
Expand Down
3 changes: 3 additions & 0 deletions packages/dtcg-parser/src/parse-dtcg-file-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export function parseDtcgFileData(dtcgData: unknown): RootGroup {
groupPropsToExtract: [dtcgPropRegex],
parseGroupData: parseGroup,
parseDesignTokenData: parseToken,
addChildToGroup(group, _name, child) {
group.addChild(child);
},
});

if (!(result instanceof RootGroup)) {
Expand Down
7 changes: 2 additions & 5 deletions packages/dtcg-parser/src/parse-group.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { type DesignToken, Group, RootGroup } from "@udt/tom";
import { Group, RootGroup } from "@udt/tom";
import { type PlainObject, type ParseGroupResult } from "@udt/parser-utils";
import { extractCommonProps } from "./extract-common-props.js";

export function parseGroup(
groupProps: PlainObject,
path: string[]
): ParseGroupResult<Group | RootGroup, DesignToken, never> {
): ParseGroupResult<Group | RootGroup, never> {
const { commonProps, rest } = extractCommonProps(groupProps);

if (Object.keys(rest).length > 0) {
Expand All @@ -22,8 +22,5 @@ export function parseGroup(

return {
group,
addChild(_name, child: Group | DesignToken) {
group.addChild(child);
},
};
}
32 changes: 18 additions & 14 deletions packages/parser-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,18 @@ const parsedData = parseData(fileData, {
// format properties
groupPropsToExtract: [ /* ... */ ];

// Function which is called for each group data object
// Function which is called for each design token
// data object that is encountered.
//
// Is given the design token data and its path, and
// should parse that data into whatever structure is
// desired.
parseDesignTokenData: (data, path, contextFromParent) => {
/* ... */
return parsedDesignToken;
},

// OPTIONAL function which is called for each group data object
// that is encountered.
//
// Is given the extracted properties of that group and its
Expand All @@ -50,24 +61,17 @@ const parsedData = parseData(fileData, {
return {
group: parsedGroup,

// optional:
addChild: (childName, childGroupOrToken) => { /*... */ },

// optional:
// OPTIONAL:
contextForChildren: /* anything you like */,
}
},

// Function which is called for each design token
// data object that is encountered.
// OPTIONAL function which is called for each design token and/or
// nested group found within a group.
//
// Is given the design token data and its path, and
// should parse that data into whatever structure is
// desired.
parseDesignTokenData: (data, path, contextFromParent) => {
/* ... */
return parsedDesignToken;
},
// Useful if your parsed groups and design tokens need to be assembled into
// a tree structure.
addChildToGroup: (parsedParentGroup, childName, parsedChildGroupOrToken) => { /*... */ },
});
```

Expand Down
4 changes: 2 additions & 2 deletions packages/parser-utils/src/extractProperties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ describe("extractProperties()", () => {
expect(extractResult.extracted).toStrictEqual({ bar: 13, baz: 666 });
});

it("returns keys of non-extracted properties", () => {
it("returns non-extracted properties", () => {
const extractResult = extractProperties(
{ foo: 42, bar: 13, baz: 666, quux: 0 },
["foo", "baz"]
);
expect(extractResult.remainingProps).toStrictEqual(["bar", "quux"]);
expect(extractResult.rest).toStrictEqual({ bar: 13, quux: 0 });
});

it("ignores props to extract that are not present in input object", () => {
Expand Down
14 changes: 7 additions & 7 deletions packages/parser-utils/src/extractProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { type PlainObject } from "./isJsonObject.js";
*/
export function extractProperties(
object: PlainObject,
propsToExtract: (string | RegExp)[]
propsToExtract: readonly (string | RegExp)[]
): {
/**
* Object containg the extract properties
Expand All @@ -28,10 +28,10 @@ export function extractProperties(
extracted: PlainObject;

/**
* Array of property names of the input
* object that were not extracted.
* Object containing the remaining, unextracted
* properties and their respective values.
*/
remainingProps: string[];
rest: PlainObject;
} {
const propNamesToExtract = propsToExtract.filter(
(prop) => typeof prop === "string"
Expand All @@ -41,7 +41,7 @@ export function extractProperties(
);

const extracted: PlainObject = {};
const remainingProps: string[] = [];
const rest: PlainObject = {};
Object.getOwnPropertyNames(object).forEach((prop) => {
if (
propNamesToExtract.some(
Expand All @@ -53,12 +53,12 @@ export function extractProperties(
) {
extracted[prop] = object[prop];
} else {
remainingProps.push(prop);
rest[prop] = object[prop];
}
});

return {
extracted,
remainingProps,
rest,
};
}
2 changes: 2 additions & 0 deletions packages/parser-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* v8 ignore start */
export * from "./parseData.js";
export * from "./isJsonObject.js";
export * from "./extractProperties.js";
/* v8 ignore end */
69 changes: 33 additions & 36 deletions packages/parser-utils/src/parseData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ interface TestDesignToken {

interface TestParentContext {
dummyData?: number;

// Used to pass in a mock addChild() function for
// mockParseGroupData() to use for testing
addChild?: AddChildFn<TestGroup, TestDesignToken>;
}

interface ParseDataCall {
Expand Down Expand Up @@ -53,7 +49,7 @@ const mockIsDesignTokenData = vi.fn<IsDesignTokenDataFn>(
);

const mockParseGroupData = vi.fn<
ParseGroupDataFn<TestGroup, TestDesignToken, TestParentContext>
ParseGroupDataFn<TestGroup, TestParentContext>
>((_data, path, contextFromParent) => {
parseDataCalls.push({
type: "group",
Expand All @@ -65,12 +61,18 @@ const mockParseGroupData = vi.fn<
},
// pass through contextFromParent
contextForChildren: contextFromParent,

// pass through addChild
addChild: contextFromParent?.addChild,
};
});

const mockAddChildToGroup = vi.fn<AddChildFn<TestGroup, TestDesignToken>>(
(_parent, name, _child) => {
parseDataCalls.push({
type: "addChild",
name,
});
}
);

const mockParseDesignTokenData = vi.fn<
ParseDesignTokenDataFn<TestDesignToken, TestParentContext>
>((_data, path, _contextFromParent) => {
Expand All @@ -81,20 +83,23 @@ const mockParseDesignTokenData = vi.fn<
return result;
});

const defaultParserConfig: ParserConfig<
TestDesignToken,
TestGroup,
TestParentContext
> = {
isDesignTokenData: mockIsDesignTokenData,
groupPropsToExtract: [],
parseGroupData: mockParseGroupData,
parseDesignTokenData: mockParseDesignTokenData,
};

describe("parseData()", () => {
const parserConfig: ParserConfig<
TestDesignToken,
TestGroup,
TestParentContext
> = {
isDesignTokenData: mockIsDesignTokenData,
groupPropsToExtract: [],
parseGroupData: mockParseGroupData,
parseDesignTokenData: mockParseDesignTokenData,
};
let parserConfig: ParserConfig<TestDesignToken, TestGroup, TestParentContext>;

beforeEach(() => {
// Reset stuff
parserConfig = defaultParserConfig;
parseDataCalls = [];
parserConfig.groupPropsToExtract = [];
mockIsDesignTokenData.mockClear();
Expand All @@ -103,14 +108,14 @@ describe("parseData()", () => {
});

describe("parsing an empty group object", () => {
let parsedGroupOrToken: TestGroup | TestDesignToken;
let parsedGroupOrToken: TestGroup | TestDesignToken | undefined;

beforeEach(() => {
parsedGroupOrToken = parseData({}, parserConfig);
});

it("returns a group", () => {
expect(parsedGroupOrToken.type).toBe("group");
expect(parsedGroupOrToken?.type).toBe("group");
});

it("calls isDesignTokenData function once", () => {
Expand Down Expand Up @@ -141,14 +146,14 @@ describe("parseData()", () => {
stuff: 123,
notAGroup: {},
};
let parsedGroupOrToken: TestGroup | TestDesignToken;
let parsedGroupOrToken: TestGroup | TestDesignToken | undefined;

beforeEach(() => {
parsedGroupOrToken = parseData(testTokenData, parserConfig);
});

it("returns a design token", () => {
expect(parsedGroupOrToken.type).toBe("token");
expect(parsedGroupOrToken?.type).toBe("token");
});

it("does not call parseGroupData function", () => {
Expand Down Expand Up @@ -320,32 +325,24 @@ describe("parseData()", () => {
});
});

describe("using addChild functions", () => {
describe("using addChildToGroup function", () => {
const testData = {
tokenA: { value: 1 },
tokenB: { value: 2 },
groupC: { tokenX: { value: 99 }, tokenY: { value: 100 } },
};
const mockAddChild = vi.fn<AddChildFn<TestGroup, TestDesignToken>>(
(name, _child) => {
parseDataCalls.push({
type: "addChild",
name,
});
}
);
const testContext: TestParentContext = { addChild: mockAddChild };

beforeEach(() => {
mockAddChild.mockClear();
parseData(testData, parserConfig, testContext);
mockAddChildToGroup.mockClear();
parserConfig.addChildToGroup = mockAddChildToGroup;
parseData(testData, parserConfig);
});

it("calls addChild for every child of every group", () => {
it("calls addChildToGroup for every child of every group", () => {
// Root group contains 3 children: "tokenA", "tokenB" and "groupC"
// "groupC" contains 2 children: "tokenX" and "tokenY"
// 3 + 2 = 5
expect(mockAddChild).toHaveBeenCalledTimes(5);
expect(mockAddChildToGroup).toHaveBeenCalledTimes(5);
});

it("adds a nested group to its parent before parsing its children", () => {
Expand Down
Loading

0 comments on commit 4076c0e

Please sign in to comment.