Skip to content

Commit

Permalink
Code Connect v1.1.4
Browse files Browse the repository at this point in the history
  • Loading branch information
figma-bot committed Sep 30, 2024
1 parent a7fab93 commit 233ec33
Show file tree
Hide file tree
Showing 28 changed files with 854 additions and 175 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for the project.
title: ''
labels: ''
labels: 'feature request'
assignees: ''
---

Expand Down
39 changes: 31 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
# Code Connect v1.1.4 (26th September 2024)

## Fixed

### React
- Fixed a Prettier bug with the interactive setup
- Removed empty enum mappings from generated Code Connect in interactive setup
- 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)
- Support mapping from an enum value to a boolean prop in CLI Assistant

## Features

### Compose
- 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.

### SwiftUI
- Updated the swift-syntax dependency to include 600.0.0 (Swift 6)

# Code Connect v1.1.3 (11th September 2024)

## Fixed
## Fixed

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

## Features

### React
- 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.

# Code Connect v1.1.2 (10th September 2024)

## Fixed
## Fixed

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

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

## Fixed

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

# Code Connect v1.1.0 (10th September 2024)
Expand Down Expand Up @@ -205,10 +228,10 @@

### React

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

### SwiftUI

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let package = Package(
.executable(name: "figma-swift", targets: ["CodeConnectCLI"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-syntax", "510.0.3"..."600.0.0-prerelease-2024-08-14"),
.package(url: "https://github.com/apple/swift-syntax", "510.0.3"..."600.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"),
.package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.49.0"),
],
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Code Connect is easy to set up, easy to maintain, type-safe, and extensible. Out
## CLI installation

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

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).

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

### `include` and `exclude`

`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.
`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.

```jsonp
{
Expand Down
7 changes: 4 additions & 3 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@figma/code-connect",
"version": "1.1.3",
"version": "1.1.4",
"description": "A tool for connecting your design system components in code with your design system in Figma",
"keywords": [],
"author": "Figma",
Expand Down Expand Up @@ -48,7 +48,7 @@
"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",
"bundle": "npm run build && npm pack && mkdir -p bundle && mv figma-code-connect*.tgz bundle",
"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",
"bundle:npm-readme:prepare": "mv README.md ../cli-README.md.bak && cp ../README.md . && npx ts-node ../scripts/make_readme_links_absolute.ts",
"bundle:npm-readme:prepare": "mv README.md ../cli-README.md.bak && cp ../README.md . && npx tsx ../scripts/make_readme_links_absolute.ts",
"bundle:npm-readme:restore": "mv ../cli-README.md.bak README.md",
"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",
"bundle:cli": "npm run build:webpack && mkdir -p bundle-cli && pkg --compress Brotli webpack-dist/figma.js",
Expand All @@ -57,7 +57,8 @@
"bundle:cli:mac:arm64": "npm run bundle:cli -- -o bundle-cli/figma-mac-arm64 --target node18-mac-arm64",
"bundle:cli:win": "npm run bundle:cli -- -o bundle-cli/figma-win --target node18-win-x64",
"publish:npm": "npm install && npm run build && npm run bundle:npm-readme:prepare && npm publish --access public; npm run bundle:npm-readme:restore",
"typecheck": "tsc --noEmit -p tsconfig-typecheck.json"
"typecheck": "tsc --noEmit -p tsconfig-typecheck.json",
"benchmarking:run": "npx tsx ./src/connect/wizard/__test__/prop_mapping/prop_mapping_benchmarking.ts"
},
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
Expand Down
2 changes: 1 addition & 1 deletion cli/src/common/updates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function maybeShowUpdateMessage() {
if (updatedVersionAvailable) {
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}.
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).`)
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).`)
}

if (message) {
Expand Down

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions cli/src/connect/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export async function createCodeConnectFromUrl({
if (response.status === 200) {
logger.info('Parsing response')
const component = findComponentsInDocument(response.data.document, nodeIds)[0]
if (component === undefined) {
exitWithError('Could not find a component in the provided URL')
}
const normalizedName = normalizeComponentName(component.name)

const payload: CreateRequestPayload = {
Expand Down
7 changes: 5 additions & 2 deletions cli/src/connect/parser_executable_types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod'
import { Intrinsic } from '../connect/intrinsics'
import { BaseCodeConnectObject } from '../connect/figma_connect'
import { ComponentTypeSignature } from '../react/parser'
import { BaseCodeConnectObject } from './figma_connect'
import { Intrinsic } from './intrinsics'

export type ParseRequestPayload = {
mode: 'PARSE'
Expand Down Expand Up @@ -141,6 +142,8 @@ export type CreateRequestPayload = {
sourceExport?: string
// A mapping of how Figma props should map to code properties
propMapping?: PropMapping
// The type signature for the component (React only)
reactTypeSignature?: ComponentTypeSignature
// Information about the Figma component. This matches the REST API (except the
// figmaNodeUrl and normalizedName fields), which should make it easier to
// implement and maintain as we can just pass it through
Expand Down
1 change: 0 additions & 1 deletion cli/src/connect/wizard/__test__/prop_mapping/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { PropMappingTestSuite } from './types'

const BASIC: PropMappingTestSuite = {
name: 'basic',
passThreshold: 1,
testCases: [
{
exportName: 'basic',
Expand Down
221 changes: 221 additions & 0 deletions cli/src/connect/wizard/__test__/prop_mapping/benchmarking_helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import fs from 'fs'
import path from 'path'
import basic from './basic'
import { generatePropMapping } from '../../prop_mapping'
import { Intrinsic } from '../../../intrinsics'
import chalk from 'chalk'

const PROP_MAPPING_TEST_SUITES = [
basic,
]

// Order important - worst to best
enum PropResultType {
FalsePositive,
Miss,
PartiallyCorrect,
Correct,
}

type ComponentResult = {
componentName: string
results: PropResultType[]
}

type ResultTotals = {
totalMappings: number
correct: number
partiallyCorrect: number
falsePositives: number
}

export type BenchmarkingSuiteResult = {
name: string
results: ComponentResult[]
}

function areIntrinsicsEqual(a: Intrinsic, b: Intrinsic) {
function replacer(key: string, value: boolean | string | string[] | Record<string, any>) {
// TODO is order of children([]) significant?
if (Array.isArray(value)) {
return value.sort()
}
if (value && typeof value === 'object') {
return Object.keys(value)
.sort()
.reduce(
(acc, key) => {
acc[key] = value[key]
return acc
},
{} as Record<string, any>,
)
}
return value
}

return JSON.stringify(a, replacer) === JSON.stringify(b, replacer)
}

function isASubsetOf(a: any, b: any): boolean {
if (a === undefined) {
return true
}
// TODO is order of children([]) significant?
if (Array.isArray(a) && Array.isArray(b)) {
return a.every((value) => b.includes(value))
}
if (a && typeof a === 'object' && b && typeof b === 'object') {
return Object.keys(a).every((subKey) => isASubsetOf(a[subKey], b[subKey]))
}
return a === b
}

function getPropMappingBenchmarkingResults(
printDiffSinceLastRun = false,
prevResults?: BenchmarkingSuiteResult[],
) {
/**
* For each suite of components, get the total number of props that have a mapping
* in the test data, as well as the count of mappings we've correctly generated.
* We then divide correct / total to get the overall success rate
*/
return PROP_MAPPING_TEST_SUITES.map(({ testCases, name }) => {
return testCases.reduce(
(suiteResults, testCase, caseIndex) => {
const componentResults: PropResultType[] = []
const actualResult = generatePropMapping({
componentPropertyDefinitions: testCase.componentPropertyDefinitions,
signature: testCase.signature,
})

// iterate expected individual prop mappings for current component
Object.keys(testCase.perfectResult).forEach((prop, propIndex) => {
let propResult = PropResultType.Miss
if (prop in actualResult) {
if (areIntrinsicsEqual(actualResult[prop], testCase.perfectResult[prop])) {
propResult = PropResultType.Correct
} else if (isASubsetOf(actualResult[prop], testCase.perfectResult[prop])) {
// TODO would be good if this also reflected
propResult = PropResultType.PartiallyCorrect
} else {
propResult = PropResultType.FalsePositive
}
}
if (printDiffSinceLastRun && prevResults) {
const prevPropResult = prevResults.find(
(prevSuite) => prevSuite.name === suiteResults.name,
)?.results[caseIndex]?.results[propIndex]
if (propResult !== prevPropResult) {
const text =
`Prop mapping result changed: ${PropResultType[prevPropResult!]} -> ${PropResultType[propResult]}` +
` (${suiteResults.name} - ${testCase.exportName} - ${prop})` +
(prop in actualResult
? `\nResult: ${JSON.stringify(actualResult[prop], null, 2)}`
: '')
if (propResult > prevPropResult!) {
console.log(chalk.green(text))
} else {
console.log(chalk.red(text))
}
}
}
componentResults.push(propResult)
})

suiteResults.results.push({
componentName: testCase.exportName,
results: componentResults,
})

return suiteResults
},
{
name,
results: [],
} as BenchmarkingSuiteResult,
)
})
}

export function runPropMappingBenchmarking() {
const prevResults = JSON.parse(
fs.readFileSync(path.join(__dirname, 'prop_mapping_benchmarking_snapshot.json'), 'utf8'),
) as BenchmarkingSuiteResult[]

const results = getPropMappingBenchmarkingResults(true, prevResults)

const hasChanged = JSON.stringify(results) !== JSON.stringify(prevResults)

return { results, prevResults, hasChanged }
}

function getSuiteTotals(suiteResult: BenchmarkingSuiteResult) {
const propResults = suiteResult.results.flatMap((r) => r.results)
return propResults.reduce(
(acc, propResult) => {
if (propResult === PropResultType.Correct) {
acc.correct++
} else if (propResult === PropResultType.PartiallyCorrect) {
acc.partiallyCorrect++
} else if (propResult === PropResultType.FalsePositive) {
acc.falsePositives++
}
acc.totalMappings++
return acc
},
{
totalMappings: 0,
correct: 0,
partiallyCorrect: 0,
falsePositives: 0,
} as ResultTotals,
)
}

export function prettyPrintBenchmarkingResults(
suiteResultsWithoutTotal: BenchmarkingSuiteResult[],
prevSuiteResultsWithoutTotal: BenchmarkingSuiteResult[],
) {
const prettyResults: Record<string, Record<string, string | number>> = {}

const addTotals = (allResults: BenchmarkingSuiteResult[]) => [
...allResults,
{
name: 'TOTAL',
results: allResults.flatMap((r) => r.results),
},
]

const suiteResults = addTotals(suiteResultsWithoutTotal)
const prevSuiteResults = addTotals(prevSuiteResultsWithoutTotal)

suiteResults.forEach((suiteResult, index) => {
const prevTotals = getSuiteTotals(prevSuiteResults[index])
const currenttotals = getSuiteTotals(suiteResult)

const printDiff = (print: (totals: ResultTotals) => string | number) => {
const prev = print(prevTotals)
const current = print(currenttotals)
return current === prev ? current : `${prev} -> ${current}`
}

prettyResults[suiteResult.name] = {
'Total Mappings': printDiff((totals) => totals.totalMappings),
Correct: printDiff(
(totals) =>
`${totals.correct} (${((totals.correct / totals.totalMappings) * 100).toFixed(1)}%)`,
),
'Partially correct': printDiff(
(totals) =>
`${totals.partiallyCorrect} (${((totals.partiallyCorrect / totals.totalMappings) * 100).toFixed(1)}%)`,
),
'False positives': printDiff(
(totals) =>
`${totals.falsePositives} (${((totals.falsePositives / totals.totalMappings) * 100).toFixed(1)}%)`,
),
}
})

console.table(prettyResults)
}
Loading

0 comments on commit 233ec33

Please sign in to comment.