Skip to content

Commit 233ec33

Browse files
author
figma-bot
committed
Code Connect v1.1.4
1 parent a7fab93 commit 233ec33

28 files changed

+854
-175
lines changed

.github/ISSUE_TEMPLATE/feature_request.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name: Feature request
33
about: Suggest an idea for the project.
44
title: ''
5-
labels: ''
5+
labels: 'feature request'
66
assignees: ''
77
---
88

CHANGELOG.md

+31-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1+
# Code Connect v1.1.4 (26th September 2024)
2+
3+
## Fixed
4+
5+
### React
6+
- Fixed a Prettier bug with the interactive setup
7+
- Removed empty enum mappings from generated Code Connect in interactive setup
8+
- Fixed an issue with props not rendering correctly in the Figma UI if used in the body of a component (e.g. as a hook argument). Any Code Connect with this issue will need republishing to be fixed. (fixes https://github.com/figma/code-connect/issues/167)
9+
- Support mapping from an enum value to a boolean prop in CLI Assistant
10+
11+
## Features
12+
13+
### Compose
14+
- The dependencies required to author Code Connect files now live in a separate module from the plugin and are hosted on Maven Central. Refer to the [documentation](docs/compose.md) for updated instructions on adding Code Connect to your project.
15+
16+
### SwiftUI
17+
- Updated the swift-syntax dependency to include 600.0.0 (Swift 6)
18+
119
# Code Connect v1.1.3 (11th September 2024)
220

3-
## Fixed
21+
## Fixed
422

523
### HTML
624
- Fixed an issue where `imports` was incorrectly not included in the TypeScript interface
@@ -9,18 +27,23 @@
927
### React
1028
- Fixed an issue where `imports` was incorrectly not included in the TypeScript interface (fixes https://github.com/figma/code-connect/issues/159)
1129

30+
## Features
31+
32+
### React
33+
- Code Connect files created in the CLI assistant will now start try to use auto-generated prop mappings in the component props. This is an early feature and support for different types is limited.
34+
1235
# Code Connect v1.1.2 (10th September 2024)
1336

14-
## Fixed
37+
## Fixed
1538

16-
### React
39+
### React
1740
- Fixed an issue with `client` export used by the icon script (fixes https://github.com/figma/code-connect/issues/156)
1841

1942
# Code Connect v1.1.1 (10th September 2024)
2043

2144
## Fixed
2245

23-
### General
46+
### General
2447
- Fixed an issue where the `@figma/[email protected]` npm package had an incorrect README
2548

2649
# Code Connect v1.1.0 (10th September 2024)
@@ -205,10 +228,10 @@
205228

206229
### React
207230

208-
- Added support for [nested properties](cli/README.md#nested-properties), using `figma.nestedProps`
209-
- Added support for [concatenating strings for CSS class names](cli/README.md#classname), using `figma.className`
210-
- Added support for [text content from layers](cli/README.md#text-content), using `figma.textContent`
211-
- Added support for [wildcards](cli/README.md#wildcard-match) with `figma.children`
231+
- Added support for [nested properties](docs/react.md#nested-properties), using `figma.nestedProps`
232+
- Added support for [concatenating strings for CSS class names](docs/react.md#classname), using `figma.className`
233+
- Added support for [text content from layers](docs/react.md#text-content), using `figma.textContent`
234+
- Added support for [wildcards](docs/react.md#wildcard-match) with `figma.children`
212235

213236
### SwiftUI
214237

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
.executable(name: "figma-swift", targets: ["CodeConnectCLI"])
1616
],
1717
dependencies: [
18-
.package(url: "https://github.com/apple/swift-syntax", "510.0.3"..."600.0.0-prerelease-2024-08-14"),
18+
.package(url: "https://github.com/apple/swift-syntax", "510.0.3"..."600.0.0"),
1919
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
2020
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.0"),
2121
],

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Code Connect is easy to set up, easy to maintain, type-safe, and extensible. Out
1111
1212
## CLI installation
1313

14-
To install Code Connect locally to a React project, you can follow the instructions in the [React README](cli/README.md#installation).
14+
To install Code Connect locally to a React project, you can follow the instructions in the [React README](docs/react.md#installation).
1515

1616
For other platforms, you first need to have Node.js v16 or newer installed on your computer. You can check if you already have Node.js installed and which version by running `node -v`. If you need to install Node.js, instructions for all platforms can be found [on the Node.js website](https://nodejs.org/en/download/package-manager).
1717

@@ -38,7 +38,7 @@ Every platform supports some common configuration options, in addition to any pl
3838

3939
### `include` and `exclude`
4040

41-
`include` and `exclude` are lists of globs for where to parse Code Connect files, and for where to search for your component code when using the [interactive setup](cli/README.md#interactive-setup). `include` and `exclude` paths must be relative to the location of the config file.
41+
`include` and `exclude` are lists of globs for where to parse Code Connect files, and for where to search for your component code when using the [interactive setup](docs/react.md#interactive-setup). `include` and `exclude` paths must be relative to the location of the config file.
4242

4343
```jsonp
4444
{

cli/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@figma/code-connect",
3-
"version": "1.1.3",
3+
"version": "1.1.4",
44
"description": "A tool for connecting your design system components in code with your design system in Figma",
55
"keywords": [],
66
"author": "Figma",
@@ -48,7 +48,7 @@
4848
"test:non-mac": "npm run test -- --testPathIgnorePatterns=e2e_parse_command_swift.test.ts --testPathIgnorePatterns=e2e_wizard_swift.test.ts --testPathIgnorePatterns=e2e_parse_command_swift_xcodeproj.test.ts",
4949
"bundle": "npm run build && npm pack && mkdir -p bundle && mv figma-code-connect*.tgz bundle",
5050
"bundle:local": "cp package.json package.json.bak && grep -v 'workspace:' package.json > package.json.tmp && mv package.json.tmp package.json && npm run build && npm pack && mkdir -p bundle-local && mv figma-code-connect*.tgz bundle-local; mv package.json.bak package.json",
51-
"bundle:npm-readme:prepare": "mv README.md ../cli-README.md.bak && cp ../README.md . && npx ts-node ../scripts/make_readme_links_absolute.ts",
51+
"bundle:npm-readme:prepare": "mv README.md ../cli-README.md.bak && cp ../README.md . && npx tsx ../scripts/make_readme_links_absolute.ts",
5252
"bundle:npm-readme:restore": "mv ../cli-README.md.bak README.md",
5353
"bundle:npm": "npm run build && npm run bundle:npm-readme:prepare && npm pack && mkdir -p bundle-npm && mv figma-code-connect*.tgz bundle-npm; npm run bundle:npm-readme:restore",
5454
"bundle:cli": "npm run build:webpack && mkdir -p bundle-cli && pkg --compress Brotli webpack-dist/figma.js",
@@ -57,7 +57,8 @@
5757
"bundle:cli:mac:arm64": "npm run bundle:cli -- -o bundle-cli/figma-mac-arm64 --target node18-mac-arm64",
5858
"bundle:cli:win": "npm run bundle:cli -- -o bundle-cli/figma-win --target node18-win-x64",
5959
"publish:npm": "npm install && npm run build && npm run bundle:npm-readme:prepare && npm publish --access public; npm run bundle:npm-readme:restore",
60-
"typecheck": "tsc --noEmit -p tsconfig-typecheck.json"
60+
"typecheck": "tsc --noEmit -p tsconfig-typecheck.json",
61+
"benchmarking:run": "npx tsx ./src/connect/wizard/__test__/prop_mapping/prop_mapping_benchmarking.ts"
6162
},
6263
"devDependencies": {
6364
"@types/cross-spawn": "^6.0.6",

cli/src/common/updates.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function maybeShowUpdateMessage() {
9393
if (updatedVersionAvailable) {
9494
logger.warn(`\nA new version of the Figma CLI is available. v${require('../../package.json').version} is currently installed, and the latest version available is v${updatedVersionAvailable}.
9595
96-
To update, run ${chalk.whiteBright('npm install @figma/code-connect@latest')} for React, or ${chalk.whiteBright('npm install -g @figma/code-connect@latest')} for other targets (or if you have Code Connect installed globally).`)
96+
To update, run ${chalk.whiteBright('npm install @figma/code-connect@latest')} for React or HTML, or ${chalk.whiteBright('npm install -g @figma/code-connect@latest')} for other targets (or if you have Code Connect installed globally).`)
9797
}
9898

9999
if (message) {

cli/src/connect/__test__/e2e/e2e_parse_command/dummy_api_response_for_wizard.json

+1-1
Large diffs are not rendered by default.

cli/src/connect/create.ts

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ export async function createCodeConnectFromUrl({
7272
if (response.status === 200) {
7373
logger.info('Parsing response')
7474
const component = findComponentsInDocument(response.data.document, nodeIds)[0]
75+
if (component === undefined) {
76+
exitWithError('Could not find a component in the provided URL')
77+
}
7578
const normalizedName = normalizeComponentName(component.name)
7679

7780
const payload: CreateRequestPayload = {

cli/src/connect/parser_executable_types.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { z } from 'zod'
2-
import { Intrinsic } from '../connect/intrinsics'
3-
import { BaseCodeConnectObject } from '../connect/figma_connect'
2+
import { ComponentTypeSignature } from '../react/parser'
3+
import { BaseCodeConnectObject } from './figma_connect'
4+
import { Intrinsic } from './intrinsics'
45

56
export type ParseRequestPayload = {
67
mode: 'PARSE'
@@ -141,6 +142,8 @@ export type CreateRequestPayload = {
141142
sourceExport?: string
142143
// A mapping of how Figma props should map to code properties
143144
propMapping?: PropMapping
145+
// The type signature for the component (React only)
146+
reactTypeSignature?: ComponentTypeSignature
144147
// Information about the Figma component. This matches the REST API (except the
145148
// figmaNodeUrl and normalizedName fields), which should make it easier to
146149
// implement and maintain as we can just pass it through

cli/src/connect/wizard/__test__/prop_mapping/basic.ts

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { PropMappingTestSuite } from './types'
44

55
const BASIC: PropMappingTestSuite = {
66
name: 'basic',
7-
passThreshold: 1,
87
testCases: [
98
{
109
exportName: 'basic',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import basic from './basic'
4+
import { generatePropMapping } from '../../prop_mapping'
5+
import { Intrinsic } from '../../../intrinsics'
6+
import chalk from 'chalk'
7+
8+
const PROP_MAPPING_TEST_SUITES = [
9+
basic,
10+
]
11+
12+
// Order important - worst to best
13+
enum PropResultType {
14+
FalsePositive,
15+
Miss,
16+
PartiallyCorrect,
17+
Correct,
18+
}
19+
20+
type ComponentResult = {
21+
componentName: string
22+
results: PropResultType[]
23+
}
24+
25+
type ResultTotals = {
26+
totalMappings: number
27+
correct: number
28+
partiallyCorrect: number
29+
falsePositives: number
30+
}
31+
32+
export type BenchmarkingSuiteResult = {
33+
name: string
34+
results: ComponentResult[]
35+
}
36+
37+
function areIntrinsicsEqual(a: Intrinsic, b: Intrinsic) {
38+
function replacer(key: string, value: boolean | string | string[] | Record<string, any>) {
39+
// TODO is order of children([]) significant?
40+
if (Array.isArray(value)) {
41+
return value.sort()
42+
}
43+
if (value && typeof value === 'object') {
44+
return Object.keys(value)
45+
.sort()
46+
.reduce(
47+
(acc, key) => {
48+
acc[key] = value[key]
49+
return acc
50+
},
51+
{} as Record<string, any>,
52+
)
53+
}
54+
return value
55+
}
56+
57+
return JSON.stringify(a, replacer) === JSON.stringify(b, replacer)
58+
}
59+
60+
function isASubsetOf(a: any, b: any): boolean {
61+
if (a === undefined) {
62+
return true
63+
}
64+
// TODO is order of children([]) significant?
65+
if (Array.isArray(a) && Array.isArray(b)) {
66+
return a.every((value) => b.includes(value))
67+
}
68+
if (a && typeof a === 'object' && b && typeof b === 'object') {
69+
return Object.keys(a).every((subKey) => isASubsetOf(a[subKey], b[subKey]))
70+
}
71+
return a === b
72+
}
73+
74+
function getPropMappingBenchmarkingResults(
75+
printDiffSinceLastRun = false,
76+
prevResults?: BenchmarkingSuiteResult[],
77+
) {
78+
/**
79+
* For each suite of components, get the total number of props that have a mapping
80+
* in the test data, as well as the count of mappings we've correctly generated.
81+
* We then divide correct / total to get the overall success rate
82+
*/
83+
return PROP_MAPPING_TEST_SUITES.map(({ testCases, name }) => {
84+
return testCases.reduce(
85+
(suiteResults, testCase, caseIndex) => {
86+
const componentResults: PropResultType[] = []
87+
const actualResult = generatePropMapping({
88+
componentPropertyDefinitions: testCase.componentPropertyDefinitions,
89+
signature: testCase.signature,
90+
})
91+
92+
// iterate expected individual prop mappings for current component
93+
Object.keys(testCase.perfectResult).forEach((prop, propIndex) => {
94+
let propResult = PropResultType.Miss
95+
if (prop in actualResult) {
96+
if (areIntrinsicsEqual(actualResult[prop], testCase.perfectResult[prop])) {
97+
propResult = PropResultType.Correct
98+
} else if (isASubsetOf(actualResult[prop], testCase.perfectResult[prop])) {
99+
// TODO would be good if this also reflected
100+
propResult = PropResultType.PartiallyCorrect
101+
} else {
102+
propResult = PropResultType.FalsePositive
103+
}
104+
}
105+
if (printDiffSinceLastRun && prevResults) {
106+
const prevPropResult = prevResults.find(
107+
(prevSuite) => prevSuite.name === suiteResults.name,
108+
)?.results[caseIndex]?.results[propIndex]
109+
if (propResult !== prevPropResult) {
110+
const text =
111+
`Prop mapping result changed: ${PropResultType[prevPropResult!]} -> ${PropResultType[propResult]}` +
112+
` (${suiteResults.name} - ${testCase.exportName} - ${prop})` +
113+
(prop in actualResult
114+
? `\nResult: ${JSON.stringify(actualResult[prop], null, 2)}`
115+
: '')
116+
if (propResult > prevPropResult!) {
117+
console.log(chalk.green(text))
118+
} else {
119+
console.log(chalk.red(text))
120+
}
121+
}
122+
}
123+
componentResults.push(propResult)
124+
})
125+
126+
suiteResults.results.push({
127+
componentName: testCase.exportName,
128+
results: componentResults,
129+
})
130+
131+
return suiteResults
132+
},
133+
{
134+
name,
135+
results: [],
136+
} as BenchmarkingSuiteResult,
137+
)
138+
})
139+
}
140+
141+
export function runPropMappingBenchmarking() {
142+
const prevResults = JSON.parse(
143+
fs.readFileSync(path.join(__dirname, 'prop_mapping_benchmarking_snapshot.json'), 'utf8'),
144+
) as BenchmarkingSuiteResult[]
145+
146+
const results = getPropMappingBenchmarkingResults(true, prevResults)
147+
148+
const hasChanged = JSON.stringify(results) !== JSON.stringify(prevResults)
149+
150+
return { results, prevResults, hasChanged }
151+
}
152+
153+
function getSuiteTotals(suiteResult: BenchmarkingSuiteResult) {
154+
const propResults = suiteResult.results.flatMap((r) => r.results)
155+
return propResults.reduce(
156+
(acc, propResult) => {
157+
if (propResult === PropResultType.Correct) {
158+
acc.correct++
159+
} else if (propResult === PropResultType.PartiallyCorrect) {
160+
acc.partiallyCorrect++
161+
} else if (propResult === PropResultType.FalsePositive) {
162+
acc.falsePositives++
163+
}
164+
acc.totalMappings++
165+
return acc
166+
},
167+
{
168+
totalMappings: 0,
169+
correct: 0,
170+
partiallyCorrect: 0,
171+
falsePositives: 0,
172+
} as ResultTotals,
173+
)
174+
}
175+
176+
export function prettyPrintBenchmarkingResults(
177+
suiteResultsWithoutTotal: BenchmarkingSuiteResult[],
178+
prevSuiteResultsWithoutTotal: BenchmarkingSuiteResult[],
179+
) {
180+
const prettyResults: Record<string, Record<string, string | number>> = {}
181+
182+
const addTotals = (allResults: BenchmarkingSuiteResult[]) => [
183+
...allResults,
184+
{
185+
name: 'TOTAL',
186+
results: allResults.flatMap((r) => r.results),
187+
},
188+
]
189+
190+
const suiteResults = addTotals(suiteResultsWithoutTotal)
191+
const prevSuiteResults = addTotals(prevSuiteResultsWithoutTotal)
192+
193+
suiteResults.forEach((suiteResult, index) => {
194+
const prevTotals = getSuiteTotals(prevSuiteResults[index])
195+
const currenttotals = getSuiteTotals(suiteResult)
196+
197+
const printDiff = (print: (totals: ResultTotals) => string | number) => {
198+
const prev = print(prevTotals)
199+
const current = print(currenttotals)
200+
return current === prev ? current : `${prev} -> ${current}`
201+
}
202+
203+
prettyResults[suiteResult.name] = {
204+
'Total Mappings': printDiff((totals) => totals.totalMappings),
205+
Correct: printDiff(
206+
(totals) =>
207+
`${totals.correct} (${((totals.correct / totals.totalMappings) * 100).toFixed(1)}%)`,
208+
),
209+
'Partially correct': printDiff(
210+
(totals) =>
211+
`${totals.partiallyCorrect} (${((totals.partiallyCorrect / totals.totalMappings) * 100).toFixed(1)}%)`,
212+
),
213+
'False positives': printDiff(
214+
(totals) =>
215+
`${totals.falsePositives} (${((totals.falsePositives / totals.totalMappings) * 100).toFixed(1)}%)`,
216+
),
217+
}
218+
})
219+
220+
console.table(prettyResults)
221+
}

0 commit comments

Comments
 (0)