diff --git a/src/domain/javascript/javascriptFeatureSeed.ts b/src/domain/javascript/javascriptFeatureSeed.ts index 2cd238487d..98fb76477d 100644 --- a/src/domain/javascript/javascriptFeatureSeed.ts +++ b/src/domain/javascript/javascriptFeatureSeed.ts @@ -273,7 +273,7 @@ const propertyFields = ( }; const normalized = (value: string, caseSensitive: boolean): string => { - const normalizedValue = value.normalize("NFKC"); + const normalizedValue = value.normalize(caseSensitive ? "NFC" : "NFKC"); return caseSensitive ? normalizedValue : normalizedValue.toLocaleLowerCase("en-US"); diff --git a/src/domain/javascript/javascriptFeatureSeed.unicode.test.ts b/src/domain/javascript/javascriptFeatureSeed.unicode.test.ts new file mode 100644 index 0000000000..4db3ab13d9 --- /dev/null +++ b/src/domain/javascript/javascriptFeatureSeed.unicode.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; + +import { + APPLICATION_GRAPH_DIGESTS, + artifactEvidence, + fixtureNode, +} from "./javascriptApplicationGraph.fixture.js"; +import { findApplicationFeatureSeeds } from "./javascriptFeatureSeed.js"; + +const channelNode = (channel: string) => + fixtureNode( + "ipc-channel", + { + strategy: "artifact-local-key", + stability: "artifact-version", + artifact_sha256: APPLICATION_GRAPH_DIGESTS.asar, + namespace: "electron-ipc", + key: channel, + }, + channel, + { channel }, + artifactEvidence( + APPLICATION_GRAPH_DIGESTS.asar, + "dist/preload.js", + "ast-static-analysis", + ), + ); + +const nodes = [channelNode("ff"), channelNode("ff")] as const; + +const matchedNodeIds = ( + value: string, + match: "exact" | "contains", + caseSensitive: boolean, +) => + findApplicationFeatureSeeds(nodes, { + kind: "channel", + value, + match, + case_sensitive: caseSensitive, + }).map(({ node_id }) => node_id); + +describe("application feature seed Unicode matching", () => { + it("keeps compatibility-distinct channels separate for exact case-sensitive seeds", () => { + expect(matchedNodeIds("ff", "exact", true)).toEqual([nodes[0].node_id]); + expect(matchedNodeIds("ff", "exact", true)).toEqual([nodes[1].node_id]); + }); + + it("does not compatibility-fold case-sensitive substring seeds", () => { + expect(matchedNodeIds("f", "contains", true)).toEqual([nodes[0].node_id]); + }); + + it("retains compatibility folding for explicit case-insensitive matching", () => { + expect(matchedNodeIds("FF", "exact", false)).toEqual( + nodes.map(({ node_id }) => node_id).sort(), + ); + }); +});