@figma/code-connect 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/README.md +2 -595
  2. package/dist/cli.js +2 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/client/figma_client.d.ts +1 -1
  5. package/dist/client/figma_client.js +9 -7
  6. package/dist/client/figma_client.js.map +1 -1
  7. package/dist/commands/connect.d.ts +4 -2
  8. package/dist/commands/connect.d.ts.map +1 -1
  9. package/dist/commands/connect.js +97 -49
  10. package/dist/commands/connect.js.map +1 -1
  11. package/dist/connect/api.d.ts +143 -0
  12. package/dist/connect/api.d.ts.map +1 -0
  13. package/dist/connect/api.js +3 -0
  14. package/dist/connect/api.js.map +1 -0
  15. package/dist/connect/create.d.ts.map +1 -1
  16. package/dist/connect/create.js +4 -0
  17. package/dist/connect/create.js.map +1 -1
  18. package/dist/connect/create_common.d.ts +7 -0
  19. package/dist/connect/create_common.d.ts.map +1 -0
  20. package/dist/connect/create_common.js +19 -0
  21. package/dist/connect/create_common.js.map +1 -0
  22. package/dist/connect/external_types.d.ts +8 -0
  23. package/dist/connect/external_types.d.ts.map +1 -0
  24. package/dist/connect/external_types.js +31 -0
  25. package/dist/connect/external_types.js.map +1 -0
  26. package/dist/connect/figma_connect.d.ts +26 -0
  27. package/dist/connect/figma_connect.d.ts.map +1 -0
  28. package/dist/connect/figma_connect.js +3 -0
  29. package/dist/connect/figma_connect.js.map +1 -0
  30. package/dist/connect/figma_rest_api.d.ts +7 -0
  31. package/dist/connect/figma_rest_api.d.ts.map +1 -1
  32. package/dist/connect/figma_rest_api.js.map +1 -1
  33. package/dist/connect/index_common.d.ts +3 -0
  34. package/dist/connect/index_common.d.ts.map +1 -0
  35. package/dist/connect/index_common.js +23 -0
  36. package/dist/connect/index_common.js.map +1 -0
  37. package/dist/connect/intrinsics.d.ts +94 -0
  38. package/dist/connect/intrinsics.d.ts.map +1 -0
  39. package/dist/connect/intrinsics.js +401 -0
  40. package/dist/connect/intrinsics.js.map +1 -0
  41. package/dist/connect/parser_common.d.ts +144 -0
  42. package/dist/connect/parser_common.d.ts.map +1 -0
  43. package/dist/connect/parser_common.js +297 -0
  44. package/dist/connect/parser_common.js.map +1 -0
  45. package/dist/connect/parser_executable_types.d.ts +20 -23
  46. package/dist/connect/parser_executable_types.d.ts.map +1 -1
  47. package/dist/connect/parser_executable_types.js.map +1 -1
  48. package/dist/connect/parser_executables.js +1 -1
  49. package/dist/connect/parser_executables.js.map +1 -1
  50. package/dist/connect/project.d.ts +20 -5
  51. package/dist/connect/project.d.ts.map +1 -1
  52. package/dist/connect/project.js +79 -16
  53. package/dist/connect/project.js.map +1 -1
  54. package/dist/connect/upload.d.ts +1 -1
  55. package/dist/connect/upload.d.ts.map +1 -1
  56. package/dist/connect/validation.d.ts +1 -1
  57. package/dist/connect/validation.d.ts.map +1 -1
  58. package/dist/connect/wizard/helpers.d.ts +1 -0
  59. package/dist/connect/wizard/helpers.d.ts.map +1 -1
  60. package/dist/connect/wizard/helpers.js +27 -15
  61. package/dist/connect/wizard/helpers.js.map +1 -1
  62. package/dist/connect/wizard/prop_mapping.d.ts +12 -7
  63. package/dist/connect/wizard/prop_mapping.d.ts.map +1 -1
  64. package/dist/connect/wizard/prop_mapping.js +88 -89
  65. package/dist/connect/wizard/prop_mapping.js.map +1 -1
  66. package/dist/connect/wizard/run_wizard.d.ts.map +1 -1
  67. package/dist/connect/wizard/run_wizard.js +8 -3
  68. package/dist/connect/wizard/run_wizard.js.map +1 -1
  69. package/dist/connect/wizard/signature_extraction.d.ts +7 -0
  70. package/dist/connect/wizard/signature_extraction.d.ts.map +1 -0
  71. package/dist/connect/wizard/signature_extraction.js +46 -0
  72. package/dist/connect/wizard/signature_extraction.js.map +1 -0
  73. package/dist/html/__test__/parser/examples/NoConfigObject.figma.d.ts +2 -0
  74. package/dist/html/__test__/parser/examples/NoConfigObject.figma.d.ts.map +1 -0
  75. package/dist/html/__test__/parser/examples/NoConfigObject.figma.js +9 -0
  76. package/dist/html/__test__/parser/examples/NoConfigObject.figma.js.map +1 -0
  77. package/dist/html/__test__/parser/examples/NoExample.figma.d.ts +2 -0
  78. package/dist/html/__test__/parser/examples/NoExample.figma.d.ts.map +1 -0
  79. package/dist/html/__test__/parser/examples/NoExample.figma.js +9 -0
  80. package/dist/html/__test__/parser/examples/NoExample.figma.js.map +1 -0
  81. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.d.ts +2 -0
  82. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.d.ts.map +1 -0
  83. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.js +33 -0
  84. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.js.map +1 -0
  85. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.d.ts +2 -0
  86. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.d.ts.map +1 -0
  87. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.js +15 -0
  88. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.js.map +1 -0
  89. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.d.ts +2 -0
  90. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.d.ts.map +1 -0
  91. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.js +15 -0
  92. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.js.map +1 -0
  93. package/dist/html/__test__/parser/parser.test.d.ts +2 -0
  94. package/dist/html/__test__/parser/parser.test.d.ts.map +1 -0
  95. package/dist/html/__test__/parser/parser.test.js +50 -0
  96. package/dist/html/__test__/parser/parser.test.js.map +1 -0
  97. package/dist/html/create.d.ts +4 -0
  98. package/dist/html/create.d.ts.map +1 -0
  99. package/dist/html/create.js +81 -0
  100. package/dist/html/create.js.map +1 -0
  101. package/dist/html/external.d.ts +8 -0
  102. package/dist/html/external.d.ts.map +1 -0
  103. package/dist/html/external.js +25 -0
  104. package/dist/html/external.js.map +1 -0
  105. package/dist/html/index_html.d.ts +77 -0
  106. package/dist/html/index_html.d.ts.map +1 -0
  107. package/dist/html/index_html.js +42 -0
  108. package/dist/html/index_html.js.map +1 -0
  109. package/dist/html/parser.d.ts +19 -0
  110. package/dist/html/parser.d.ts.map +1 -0
  111. package/dist/html/parser.js +487 -0
  112. package/dist/html/parser.js.map +1 -0
  113. package/dist/html/parser_template_helpers.d.ts +2 -0
  114. package/dist/html/parser_template_helpers.d.ts.map +1 -0
  115. package/dist/html/parser_template_helpers.js +68 -0
  116. package/dist/html/parser_template_helpers.js.map +1 -0
  117. package/dist/html/template_literal.d.ts +5 -0
  118. package/dist/html/template_literal.d.ts.map +1 -0
  119. package/dist/html/template_literal.js +8 -0
  120. package/dist/html/template_literal.js.map +1 -0
  121. package/dist/parser_scripts/get_swift_parser_dir.d.ts +1 -1
  122. package/dist/parser_scripts/get_swift_parser_dir.d.ts.map +1 -1
  123. package/dist/parser_scripts/get_swift_parser_dir.js +38 -10
  124. package/dist/parser_scripts/get_swift_parser_dir.js.map +1 -1
  125. package/dist/react/create.d.ts +5 -0
  126. package/dist/react/create.d.ts.map +1 -1
  127. package/dist/react/create.js +144 -42
  128. package/dist/react/create.js.map +1 -1
  129. package/dist/react/external.d.ts +9 -0
  130. package/dist/react/external.d.ts.map +1 -0
  131. package/dist/react/external.js +45 -0
  132. package/dist/react/external.js.map +1 -0
  133. package/dist/react/index_react.d.ts +94 -0
  134. package/dist/react/index_react.d.ts.map +1 -0
  135. package/dist/react/index_react.js +39 -0
  136. package/dist/react/index_react.js.map +1 -0
  137. package/dist/react/parser.d.ts +6 -38
  138. package/dist/react/parser.d.ts.map +1 -1
  139. package/dist/react/parser.js +141 -298
  140. package/dist/react/parser.js.map +1 -1
  141. package/dist/react/parser_template_helpers.d.ts +15 -15
  142. package/dist/react/parser_template_helpers.d.ts.map +1 -1
  143. package/dist/react/parser_template_helpers.js +51 -35
  144. package/dist/react/parser_template_helpers.js.map +1 -1
  145. package/dist/storybook/convert.d.ts +1 -1
  146. package/dist/storybook/convert.d.ts.map +1 -1
  147. package/dist/storybook/convert.js +6 -4
  148. package/dist/storybook/convert.js.map +1 -1
  149. package/dist/storybook/external.d.ts +1 -1
  150. package/dist/storybook/external.d.ts.map +1 -1
  151. package/dist/typescript/compiler.d.ts +2 -2
  152. package/dist/typescript/compiler.d.ts.map +1 -1
  153. package/dist/typescript/compiler.js +51 -24
  154. package/dist/typescript/compiler.js.map +1 -1
  155. package/package.json +28 -8
@@ -3,42 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.parse = exports.getDefaultTemplate = exports.parseRenderFunction = exports.isFigmaConnectFile = exports.parseComponentMetadata = exports.extractComponentTypeSignature = exports.InternalError = exports.ParserError = void 0;
6
+ exports.parseReactDoc = exports.getDefaultTemplate = exports.parseRenderFunction = exports.parseComponentMetadata = exports.extractComponentTypeSignature = exports.findAndResolveImports = void 0;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
8
  const project_1 = require("../connect/project");
9
9
  const logging_1 = require("../common/logging");
10
10
  const compiler_1 = require("../typescript/compiler");
11
- const intrinsics_1 = require("../common/intrinsics");
11
+ const intrinsics_1 = require("../connect/intrinsics");
12
12
  const parser_template_helpers_1 = require("./parser_template_helpers");
13
- class ParserError extends Error {
14
- constructor(message, context) {
15
- super(message);
16
- this.name = 'ParserError';
17
- this.sourceFileName = context?.sourceFile.fileName || '';
18
- this.sourceFilePosition =
19
- context && context.node
20
- ? getPositionInSourceFile(context.node, context.sourceFile) || null
21
- : null;
22
- }
23
- toString() {
24
- let msg = `${(0, logging_1.highlight)((0, logging_1.error)(this.name))}: ${this.message}\n`;
25
- if (this.sourceFileName && this.sourceFilePosition) {
26
- msg += ` -> ${(0, logging_1.reset)(this.sourceFileName)}:${this.sourceFilePosition.line}:${this.sourceFilePosition.character}\n`;
27
- }
28
- return msg;
29
- }
30
- toDebugString() {
31
- return this.toString() + `\n ${this.stack}`;
32
- }
33
- }
34
- exports.ParserError = ParserError;
35
- class InternalError extends ParserError {
36
- constructor(message) {
37
- super(message);
38
- this.name = 'InternalError';
39
- }
40
- }
41
- exports.InternalError = InternalError;
13
+ const parser_common_1 = require("../connect/parser_common");
42
14
  /**
43
15
  * Traverses the AST and returns the first JSX element it finds
44
16
  * @param node AST node
@@ -60,20 +32,6 @@ function findBlock(node) {
60
32
  return typescript_1.default.forEachChild(node, findBlock);
61
33
  }
62
34
  }
63
- function findDescendants(node, cb) {
64
- const matches = [];
65
- function visit(node) {
66
- if (cb(node)) {
67
- matches.push(node);
68
- }
69
- typescript_1.default.forEachChild(node, visit);
70
- }
71
- visit(node);
72
- return matches;
73
- }
74
- function getPositionInSourceFile(node, sourceFile) {
75
- return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
76
- }
77
35
  /**
78
36
  * Walks up the AST from an assignment to find the import declaration
79
37
  */
@@ -97,6 +55,33 @@ function getImportsOfModule(sourceFile) {
97
55
  visit(sourceFile);
98
56
  return imports;
99
57
  }
58
+ function resolveModuleSpecifier(program, sourceFile, importSpecifier) {
59
+ const compilerHost = typescript_1.default.createCompilerHost(program.getCompilerOptions());
60
+ const moduleResolutionHost = {
61
+ fileExists: compilerHost.fileExists,
62
+ readFile: compilerHost.readFile,
63
+ directoryExists: compilerHost.directoryExists,
64
+ getCurrentDirectory: compilerHost.getCurrentDirectory,
65
+ getDirectories: compilerHost.getDirectories,
66
+ };
67
+ const resolvedModule = typescript_1.default.resolveModuleName(importSpecifier, sourceFile.fileName, program.getCompilerOptions(), moduleResolutionHost);
68
+ return resolvedModule.resolvedModule?.resolvedFileName;
69
+ }
70
+ // Traverse the source file and resolve imports
71
+ function findAndResolveImports(program, sourceFile) {
72
+ const importSpecifierToFilePath = {};
73
+ typescript_1.default.forEachChild(sourceFile, (node) => {
74
+ if (typescript_1.default.isImportDeclaration(node)) {
75
+ const importSpecifier = node.moduleSpecifier.text;
76
+ const resolvedFileName = resolveModuleSpecifier(program, sourceFile, importSpecifier);
77
+ if (resolvedFileName) {
78
+ importSpecifierToFilePath[importSpecifier] = resolvedFileName;
79
+ }
80
+ }
81
+ });
82
+ return importSpecifierToFilePath;
83
+ }
84
+ exports.findAndResolveImports = findAndResolveImports;
100
85
  /**
101
86
  * Finds all import statements in a file that matches the given identifiers
102
87
  *
@@ -104,13 +89,15 @@ function getImportsOfModule(sourceFile) {
104
89
  * @param identifiers List of identifiers to find imports for
105
90
  * @returns
106
91
  */
107
- function getImportsForIdentifiers({ sourceFile }, _identifiers) {
92
+ function getSourceFilesOfImportedIdentifiers(parserContext, _identifiers) {
93
+ const { sourceFile, resolvedImports } = parserContext;
108
94
  const importDeclarations = getImportsOfModule(sourceFile);
109
95
  const imports = [];
110
96
  const identifiers = _identifiers.map((identifier) => identifier.split('.')[0]);
111
97
  for (const declaration of importDeclarations) {
112
98
  let statement = declaration.getText();
113
- const file = declaration.getSourceFile();
99
+ // remove the quotation marks around the module specifier
100
+ const moduleSpecifier = declaration.moduleSpecifier.getText().slice(1, -1);
114
101
  if (declaration.importClause) {
115
102
  // Default imports
116
103
  if (declaration.importClause.name) {
@@ -118,7 +105,7 @@ function getImportsForIdentifiers({ sourceFile }, _identifiers) {
118
105
  if (identifiers.includes(identifier)) {
119
106
  imports.push({
120
107
  statement,
121
- file: file.fileName,
108
+ file: resolvedImports[moduleSpecifier],
122
109
  });
123
110
  }
124
111
  }
@@ -133,7 +120,7 @@ function getImportsForIdentifiers({ sourceFile }, _identifiers) {
133
120
  if (elements.length > 0) {
134
121
  imports.push({
135
122
  statement: statement.replace(/{.*}/s, `{ ${elements.join(', ')} }`),
136
- file: file.fileName,
123
+ file: resolvedImports[moduleSpecifier],
137
124
  });
138
125
  }
139
126
  }
@@ -143,7 +130,7 @@ function getImportsForIdentifiers({ sourceFile }, _identifiers) {
143
130
  if (identifiers.includes(identifier)) {
144
131
  imports.push({
145
132
  statement,
146
- file: file.fileName,
133
+ file: resolvedImports[moduleSpecifier],
147
134
  });
148
135
  }
149
136
  }
@@ -193,6 +180,25 @@ function getDeclarationFromSymbol(symbol) {
193
180
  }
194
181
  return symbol.declarations[0];
195
182
  }
183
+ function resolveIdentifierSymbol(identifier, checker) {
184
+ const componentSymbol = checker.getSymbolAtLocation(identifier);
185
+ if (!componentSymbol) {
186
+ throw new Error(`Symbol not found at location ${identifier.getText()}`);
187
+ }
188
+ const componentDeclaration = getDeclarationFromSymbol(componentSymbol);
189
+ if (typescript_1.default.isImportSpecifier(componentDeclaration) || typescript_1.default.isImportClause(componentDeclaration)) {
190
+ let importDeclaration = findParentImportDeclaration(componentDeclaration);
191
+ if (!importDeclaration) {
192
+ throw new Error('No import statement found for component');
193
+ }
194
+ // The component should be imported from another file, we need to follow the
195
+ // aliased symbol to get the correct function definition
196
+ if (componentSymbol.flags & typescript_1.default.SymbolFlags.Alias) {
197
+ return checker.getAliasedSymbol(componentSymbol);
198
+ }
199
+ }
200
+ return componentSymbol;
201
+ }
196
202
  /**
197
203
  * This handles a number of cases for finding the function expression of an exported symbol:
198
204
  * - A function that is exported directly (function declaration)
@@ -219,17 +225,24 @@ function findFunctionExpression(declaration, checker, sourceFile) {
219
225
  // Example: export const MemoButton = memo(Button)
220
226
  // Example: export const ButtonWithRef = forwardRef(Button)
221
227
  if (typescript_1.default.isVariableDeclaration(declaration)) {
222
- const callExpression = findCallExpression(declaration);
223
- const component = callExpression.arguments[0];
224
- // follow the symbol to its declaration, then try to find the function expression again
225
- const componentSymbol = checker.getSymbolAtLocation(component);
228
+ let componentSymbol = undefined;
229
+ // Example: export const SomeAlias = Button
230
+ if (declaration.initializer && typescript_1.default.isIdentifier(declaration.initializer)) {
231
+ componentSymbol = resolveIdentifierSymbol(declaration.initializer, checker);
232
+ }
233
+ else {
234
+ const callExpression = findCallExpression(declaration);
235
+ const component = callExpression.arguments[0];
236
+ // follow the symbol to its declaration, then try to find the function expression again
237
+ componentSymbol = checker.getSymbolAtLocation(component);
238
+ }
226
239
  if (!componentSymbol) {
227
240
  throw new Error('No symbol found at location');
228
241
  }
229
242
  const componentDeclaration = getDeclarationFromSymbol(componentSymbol);
230
243
  return findFunctionExpression(componentDeclaration, checker, sourceFile);
231
244
  }
232
- throw new ParserError('Failed to find function expression for component', {
245
+ throw new parser_common_1.ParserError('Failed to find function expression for component', {
233
246
  sourceFile,
234
247
  node: declaration,
235
248
  });
@@ -250,10 +263,25 @@ function findFunctionExpression(declaration, checker, sourceFile) {
250
263
  function extractComponentTypeSignature(symbol, checker, sourceFile) {
251
264
  const declaration = getDeclarationFromSymbol(symbol);
252
265
  const ReactInterfaceNames = ['HTMLAttributes', 'Attributes', 'AriaAttributes', 'DOMAttributes'];
253
- const functionExpression = findFunctionExpression(declaration, checker, sourceFile);
254
- const propsType = checker.getTypeAtLocation(functionExpression.parameters[0]);
266
+ let propsType = null;
267
+ /**
268
+ * Special case for forwardRef as type is passed as generic arg
269
+ */
270
+ if (typescript_1.default.isVariableDeclaration(declaration)) {
271
+ const callExpression = findCallExpression(declaration);
272
+ if ((callExpression?.expression.getText() === 'forwardRef' ||
273
+ callExpression?.expression.getText() === 'React.forwardRef') &&
274
+ callExpression.typeArguments &&
275
+ callExpression.typeArguments.length === 2) {
276
+ propsType = checker.getTypeAtLocation(callExpression.typeArguments[1]);
277
+ }
278
+ }
279
+ if (!propsType) {
280
+ const functionExpression = findFunctionExpression(declaration, checker, sourceFile);
281
+ propsType = checker.getTypeAtLocation(functionExpression.parameters[0]);
282
+ }
255
283
  if (!propsType) {
256
- throw new InternalError(`Failed to extract props from component declaration: ${declaration.getText()}`);
284
+ throw new parser_common_1.InternalError(`Failed to extract props from component declaration: ${declaration.getText()}`);
257
285
  }
258
286
  const propsMap = {};
259
287
  const props = propsType.getProperties();
@@ -297,7 +325,8 @@ exports.extractComponentTypeSignature = extractComponentTypeSignature;
297
325
  * @param node The node being parsed. Used for error logging.
298
326
  * @returns Metadata object
299
327
  */
300
- async function parseComponentMetadata(node, { checker, sourceFile, absPath }, silent) {
328
+ async function parseComponentMetadata(node, parserContext, silent) {
329
+ const { checker, sourceFile } = parserContext;
301
330
  let componentSymbol = checker.getSymbolAtLocation(node);
302
331
  let componentSourceFile = sourceFile;
303
332
  let component = '';
@@ -306,7 +335,7 @@ async function parseComponentMetadata(node, { checker, sourceFile, absPath }, si
306
335
  if (typescript_1.default.isPropertyAccessExpression(node)) {
307
336
  componentSymbol = checker.getSymbolAtLocation(node.expression);
308
337
  if (!componentSymbol) {
309
- throw new ParserError(`Could not find symbol for component ${node.expression.getText()}`, {
338
+ throw new parser_common_1.ParserError(`Could not find symbol for component ${node.expression.getText()}`, {
310
339
  sourceFile,
311
340
  node,
312
341
  });
@@ -319,7 +348,7 @@ async function parseComponentMetadata(node, { checker, sourceFile, absPath }, si
319
348
  typescript_1.default.isImportClause(componentSymbol.declarations[0]))) {
320
349
  let importDeclaration = findParentImportDeclaration(componentSymbol.declarations[0]);
321
350
  if (!importDeclaration) {
322
- throw new ParserError('No import statement found for component, make sure the component is imported', {
351
+ throw new parser_common_1.ParserError('No import statement found for component, make sure the component is imported', {
323
352
  sourceFile,
324
353
  node,
325
354
  });
@@ -363,17 +392,17 @@ async function parseComponentMetadata(node, { checker, sourceFile, absPath }, si
363
392
  }
364
393
  const source = componentSourceFile.fileName;
365
394
  if (!source) {
366
- throw new InternalError(`Could not find source file for component ${component} - is this file included in the directory passed to \`figma connect <dir>\`?`);
395
+ throw new parser_common_1.InternalError(`Could not find source file for component ${component} - is this file included in the directory passed to \`figma connect <dir>\`?`);
367
396
  }
368
397
  if (!componentDeclaration) {
369
- throw new ParserError(`Could not find declaration for component ${component}`, {
398
+ throw new parser_common_1.ParserError(`Could not find declaration for component ${component}`, {
370
399
  sourceFile,
371
400
  node,
372
401
  });
373
402
  }
374
- const line = getPositionInSourceFile(componentDeclaration, componentSourceFile).line;
403
+ const line = (0, parser_common_1.getPositionInSourceFile)(componentDeclaration, componentSourceFile).line;
375
404
  if (line === undefined) {
376
- throw new InternalError(`Could not determine line number for component ${componentDeclaration.getStart(sourceFile)}`);
405
+ throw new parser_common_1.InternalError(`Could not determine line number for component ${componentDeclaration.getStart(sourceFile)}`);
377
406
  }
378
407
  return {
379
408
  source,
@@ -382,104 +411,6 @@ async function parseComponentMetadata(node, { checker, sourceFile, absPath }, si
382
411
  };
383
412
  }
384
413
  exports.parseComponentMetadata = parseComponentMetadata;
385
- /**
386
- * Checks if an AST node is a `figma.connect()` call
387
- *
388
- * @param node AST node
389
- * @param sourceFile Source file
390
- * @returns True if the node is a `figma.connect()` call
391
- */
392
- function isFigmaConnectCall(node, sourceFile) {
393
- return (typescript_1.default.isCallExpression(node) && node.expression.getText(sourceFile).includes(intrinsics_1.FIGMA_CONNECT_CALL));
394
- }
395
- /**
396
- * Checks if a file contains Code Connect by looking for the `figma.connect()` function call
397
- *
398
- * @param program
399
- * @param file
400
- * @returns
401
- */
402
- function isFigmaConnectFile(program, file) {
403
- // We don't support Code Connect in JSX and this throws an error if we let it proceed
404
- if (!file.endsWith('.tsx')) {
405
- return false;
406
- }
407
- const sourceFile = program.getSourceFile(file);
408
- if (!sourceFile) {
409
- throw new InternalError(`Could not find source file for ${file}`);
410
- }
411
- return (findDescendants(sourceFile, (node) => {
412
- if (isFigmaConnectCall(node, sourceFile)) {
413
- return true;
414
- }
415
- return false;
416
- }).length > 0);
417
- }
418
- exports.isFigmaConnectFile = isFigmaConnectFile;
419
- /**
420
- * Parses the `links` field of a `figma.connect()` call
421
- *
422
- * @param linksArray an ArrayLiteralExpression
423
- * @param parserContext Parser context
424
- * @returns An array of link objects
425
- */
426
- function parseLinks(linksArray, parserContext) {
427
- const { sourceFile } = parserContext;
428
- const links = [];
429
- for (const element of linksArray.elements) {
430
- (0, compiler_1.assertIsObjectLiteralExpression)(element, sourceFile, `'links' must be an array literal with objects of the format { name: string, url: string }`);
431
- const name = (0, compiler_1.parsePropertyOfType)({
432
- objectLiteralNode: element,
433
- propertyName: 'name',
434
- predicate: typescript_1.default.isStringLiteral,
435
- parserContext,
436
- required: true,
437
- errorMessage: "The 'name' property must be a string literal",
438
- });
439
- const url = (0, compiler_1.parsePropertyOfType)({
440
- objectLiteralNode: element,
441
- propertyName: 'url',
442
- predicate: typescript_1.default.isStringLiteral,
443
- parserContext,
444
- required: true,
445
- errorMessage: "The 'url' property must be a string literal",
446
- });
447
- if (name && url) {
448
- links.push({ name: (0, compiler_1.stripQuotes)(name), url: (0, compiler_1.stripQuotes)(url) });
449
- }
450
- }
451
- return links;
452
- }
453
- function parseVariant(variantMap, sourceFile, checker) {
454
- return (0, compiler_1.convertObjectLiteralToJs)(variantMap, sourceFile, checker, (valueNode) => {
455
- if (!typescript_1.default.isObjectLiteralElement(valueNode) &&
456
- !typescript_1.default.isStringLiteral(valueNode) &&
457
- !typescript_1.default.isNumericLiteral(valueNode) &&
458
- valueNode.kind !== typescript_1.default.SyntaxKind.TrueKeyword &&
459
- valueNode.kind !== typescript_1.default.SyntaxKind.FalseKeyword) {
460
- throw new ParserError(`Invalid value for variant, got: ${valueNode.getText()}`, {
461
- node: valueNode,
462
- sourceFile,
463
- });
464
- }
465
- });
466
- }
467
- /**
468
- * Parses the `imports` field of a `figma.connect()` call
469
- *
470
- * @param importsArray an ArrayLiteralExpression
471
- * @param parserContext Parser context
472
- * @returns An array of link objects
473
- */
474
- function parseImports(importsArray, parserContext) {
475
- const { sourceFile } = parserContext;
476
- const imports = [];
477
- for (const element of importsArray.elements) {
478
- (0, compiler_1.assertIsStringLiteral)(element, sourceFile, `'imports' must be an array literal with strings`);
479
- imports.push((0, compiler_1.stripQuotes)(element));
480
- }
481
- return imports;
482
- }
483
414
  /**
484
415
  * Parses the render function passed to `figma.connect()`, extracting the code and
485
416
  * any import statements matching the JSX elements used in the function body
@@ -494,42 +425,23 @@ function parseRenderFunction(exp, parserContext, propMappings) {
494
425
  const { sourceFile } = parserContext;
495
426
  let exampleCode;
496
427
  if (exp.parameters.length > 1) {
497
- throw new ParserError(`Expected a single props parameter for the render function, got ${exp.parameters.length} parameters`, { sourceFile, node: exp });
428
+ throw new parser_common_1.ParserError(`Expected a single props parameter for the render function, got ${exp.parameters.length} parameters`, { sourceFile, node: exp });
498
429
  }
499
430
  const propsParameter = exp.parameters[0];
500
431
  // Keep track of any props which are referenced in the example so that we can
501
432
  // insert the appropriate `figma.properties` call in the JS template
502
433
  const referencedProps = new Set();
503
- function createPropPlaceholder({ name, node, wrapInJsxExpression = false, }) {
504
- let propReferenceName = name;
505
- // for nested prop references like `nested.prop`, we only want to look for
506
- // the prop mapping of `nested`, but include the full `nested.prop` in the
507
- // __PROP__ call
508
- if (name.includes('.')) {
509
- propReferenceName = name.split('.')[0];
510
- }
511
- const mappedProp = propMappings && propMappings[propReferenceName];
512
- if (!mappedProp) {
513
- throw new ParserError(`Could not find prop mapping for ${propReferenceName} in the props object`, {
514
- sourceFile,
515
- node,
516
- });
517
- }
518
- referencedProps.add(propReferenceName);
519
- const callExpression = typescript_1.default.factory.createCallExpression(typescript_1.default.factory.createIdentifier('__PROP__'), undefined, [typescript_1.default.factory.createStringLiteral(name)]);
520
- if (wrapInJsxExpression) {
521
- return typescript_1.default.factory.createJsxExpression(undefined, callExpression);
522
- }
523
- else {
524
- return callExpression;
525
- }
526
- }
434
+ const createPropPlaceholder = (0, parser_common_1.makeCreatePropPlaceholder)({
435
+ propMappings,
436
+ referencedProps,
437
+ sourceFile,
438
+ });
527
439
  // Find all property access expressions in the function body and replace them
528
440
  // with a function call like `__PROP__("propName")`, so that we can easily
529
441
  // find them in the next step to convert them into
530
442
  // `${_fcc_renderReactProp(...)` in the template string.
531
443
  //
532
- // Doing it this way means we can normalize the different ways in which props
444
+ // Doing it this way means we can normalize the different ways in which props
533
445
  // can be accessed using the compiler API, which is much easier than using a
534
446
  // regex, then in the next step we can use a simple regex to convert that into
535
447
  // the template string.
@@ -537,33 +449,14 @@ function parseRenderFunction(exp, parserContext, propMappings) {
537
449
  exp = typescript_1.default.transform(exp, [
538
450
  (context) => (rootNode) => {
539
451
  function visit(node) {
540
- // `props.` notation
541
- if (typescript_1.default.isIdentifier(propsParameter.name) &&
542
- typescript_1.default.isPropertyAccessExpression(node) &&
543
- node.expression.getText().startsWith(propsParameter.name.getText())) {
544
- // nested notation e.g `props.nested.prop`
545
- if (typescript_1.default.isPropertyAccessExpression(node.expression)) {
546
- const name = `${node.expression.name.getText()}.${node.name.getText()}`;
547
- return createPropPlaceholder({ name, node });
548
- }
549
- const name = node.name.getText();
550
- return createPropPlaceholder({ name, node });
551
- }
552
- // `props[""]` notation
553
- if (typescript_1.default.isIdentifier(propsParameter.name) &&
554
- typescript_1.default.isElementAccessExpression(node) &&
555
- node.expression.getText().startsWith(propsParameter.name.getText()) &&
556
- typescript_1.default.isStringLiteral(node.argumentExpression)) {
557
- const name = (0, compiler_1.stripQuotes)(node.argumentExpression);
558
- return createPropPlaceholder({ name, node });
559
- }
560
- // object destructuring references
561
- if (typescript_1.default.isObjectBindingPattern(propsParameter.name) &&
562
- typescript_1.default.isJsxExpression(node) &&
563
- node.expression &&
564
- propsParameter.name.elements.find((el) => node.expression?.getText().startsWith(el.name.getText()))) {
565
- const name = node.expression.getText();
566
- return createPropPlaceholder({ name, node, wrapInJsxExpression: true });
452
+ const visitResult = (0, parser_common_1.visitPropReferencingNode)({
453
+ propsParameter,
454
+ node,
455
+ createPropPlaceholder,
456
+ useJsx: true,
457
+ });
458
+ if (visitResult) {
459
+ return visitResult;
567
460
  }
568
461
  // object assignment using destructured reference, e.g `prop={{ key: value }}`
569
462
  // (`{{ key: props.value }}` syntax will be captured by the `props.` notation branch above)
@@ -641,7 +534,7 @@ function parseRenderFunction(exp, parserContext, propMappings) {
641
534
  exampleCode = printer.printNode(typescript_1.default.EmitHint.Unspecified, functionExpression, sourceFile);
642
535
  }
643
536
  else {
644
- throw new ParserError(`Expected a single JSX element or a block statement in the render function, got ${exp.getText()}`, { sourceFile, node: exp });
537
+ throw new parser_common_1.ParserError(`Expected a single JSX element or a block statement in the render function, got ${exp.getText()}`, { sourceFile, node: exp });
645
538
  }
646
539
  let templateCode = '';
647
540
  // Replace React prop placeholders we inserted above (like
@@ -660,9 +553,10 @@ function parseRenderFunction(exp, parserContext, propMappings) {
660
553
  exampleCode = exampleCode.replace(/\{__PROP__\("([A-Za-z_\.]+)"\)\}/g, (_match, figmaPropName) => {
661
554
  return `\${_fcc_renderReactChildren(${figmaPropName})}`;
662
555
  });
663
- // Replace object values like `{ prop: __PROP__("propName") }` with
664
- // `{ prop: ${propName} }`. These never need special treatment based on their type.
665
- exampleCode = exampleCode.replace(/([A-Za-z0-9]+):\s+__PROP__\("([A-Za-z_\.]+)"\)/g, (_match, objectKey, figmaPropName) => {
556
+ // Replace object values like `{ prop: __PROP__("propName") }` or `{
557
+ // 'aria-prop': __PROP__("propName") }` with `{ prop: ${propName} }`. These
558
+ // never need special treatment based on their type.
559
+ exampleCode = exampleCode.replace(/([^\s]+):\s+__PROP__\("([A-Za-z_\.]+)"\)/g, (_match, objectKey, figmaPropName) => {
666
560
  return `${objectKey}: \${_fcc_renderPropValue(${figmaPropName})}`;
667
561
  });
668
562
  // Generate the template code
@@ -670,41 +564,22 @@ function parseRenderFunction(exp, parserContext, propMappings) {
670
564
  templateCode = (0, parser_template_helpers_1.getParsedTemplateHelpersString)() + '\n\n';
671
565
  // Require the template API
672
566
  templateCode += `const figma = require('figma')\n\n`;
673
- let nestedLayerCount = 0;
674
567
  // Then we output `const propName = figma.properties.<kind>('propName')` calls
675
568
  // for each referenced prop, so these are accessible to the template code.
676
- if (propMappings && referencedProps.size > 0) {
677
- referencedProps.forEach((prop) => {
678
- const propMapping = propMappings[prop];
679
- if (!propMapping) {
680
- throw new ParserError(`Could not find prop mapping for ${prop}`, {
681
- sourceFile,
682
- node: exp,
683
- });
684
- }
685
- if (propMapping.kind === intrinsics_1.IntrinsicKind.NestedProps) {
686
- // the actual layer name in figma could have a bunch of special characters in it,
687
- // and if we try to normalize it to a valid JS identifier, it could conflict with
688
- // other variables in the template code. So we generate a unique variable name
689
- // for each nested layer reference instead.
690
- const nestedLayerRef = `nestedLayer${nestedLayerCount++}`;
691
- templateCode += `const ${nestedLayerRef} = figma.currentLayer.__find__("${propMapping.args.layer}")\n`;
692
- templateCode += `const ${prop} = {
693
- ${Object.entries(propMapping.args.props).map(([key, intrinsic]) => `${key}: ${(0, intrinsics_1.intrinsicToString)(intrinsic, nestedLayerRef)}\n`)}
694
- }\n`;
695
- }
696
- else {
697
- templateCode += `const ${prop} = ${(0, intrinsics_1.intrinsicToString)(propMapping)}\n`;
698
- }
699
- });
700
- templateCode += '\n';
701
- }
569
+ templateCode += (0, parser_common_1.getReferencedPropsForTemplate)({
570
+ propMappings,
571
+ referencedProps,
572
+ exp,
573
+ sourceFile,
574
+ });
575
+ // Escape backticks from the example code, as otherwise those would terminate the `figma.tsx` template literal
576
+ exampleCode = exampleCode.replace(/`/g, '\\`');
702
577
  // Finally, output the example code
703
578
  templateCode += `export default figma.tsx\`${exampleCode}\`\n`;
704
579
  // Find all JSX elements in the function body and extract their import
705
580
  // statements
706
- const jsxTags = findDescendants(exp, (element) => typescript_1.default.isJsxElement(element) || typescript_1.default.isJsxSelfClosingElement(element)).map(compiler_1.getTagName);
707
- const imports = getImportsForIdentifiers(parserContext, jsxTags);
581
+ const jsxTags = (0, parser_common_1.findDescendants)(exp, (element) => typescript_1.default.isJsxElement(element) || typescript_1.default.isJsxSelfClosingElement(element));
582
+ const imports = getSourceFilesOfImportedIdentifiers(parserContext, jsxTags.map(compiler_1.getTagName));
708
583
  return {
709
584
  code: templateCode,
710
585
  imports,
@@ -730,7 +605,7 @@ function followIdentifierToStringLiteralDeclaration(node, parserContext, errMess
730
605
  // If we followed the identifier to its declaration and it's not a string literal,
731
606
  // throw an error
732
607
  if (!result || !typescript_1.default.isStringLiteral(result)) {
733
- throw new ParserError(errMessage, {
608
+ throw new parser_common_1.ParserError(errMessage, {
734
609
  node: result,
735
610
  sourceFile: parserContext.sourceFile,
736
611
  });
@@ -854,17 +729,16 @@ function getDefaultTemplate(componentMetadata) {
854
729
  return `const figma = require("figma")\n\nexport default figma.tsx\`${example}\``;
855
730
  }
856
731
  exports.getDefaultTemplate = getDefaultTemplate;
857
- async function parseDoc(node, parserContext, { repoUrl, silent }) {
732
+ async function parseReactDoc(node, parserContext, { repoUrl, silent }) {
858
733
  const { checker, sourceFile, config } = parserContext;
859
734
  // Parse the arguments to the `Figma.connect()` call
860
735
  const { componentArg, figmaNodeUrlArg, configObjArg } = parseFigmaConnectArgs(node, parserContext);
861
- // The ones with ! are definitely defined because their parser fn has required: true,
862
- // but I couldn't work out how to model that in TypeScript
863
736
  const { propsArg, exampleArg, variantArg, linksArg, importsArg } = parseConfigObjectArg(configObjArg, parserContext);
864
- let figmaNode = (0, compiler_1.stripQuotes)(figmaNodeUrlArg);
737
+ let figmaNode = (0, compiler_1.stripQuotesFromNode)(figmaNodeUrlArg);
865
738
  // TODO This logic is duplicated in connect.ts transformDocFromParser due to some type issues
866
739
  if (config.documentUrlSubstitutions) {
867
740
  Object.entries(config.documentUrlSubstitutions).forEach(([from, to]) => {
741
+ // @ts-expect-error
868
742
  figmaNode = figmaNode.replace(from, to);
869
743
  });
870
744
  }
@@ -873,11 +747,11 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
873
747
  : undefined;
874
748
  const props = propsArg ? (0, intrinsics_1.parsePropsObject)(propsArg, parserContext) : undefined;
875
749
  const render = exampleArg ? parseRenderFunction(exampleArg, parserContext, props) : undefined;
876
- const variant = variantArg ? parseVariant(variantArg, sourceFile, checker) : undefined;
877
- const links = linksArg ? parseLinks(linksArg, parserContext) : undefined;
878
- let resolvedImports;
750
+ const variant = variantArg ? (0, parser_common_1.parseVariant)(variantArg, sourceFile, checker) : undefined;
751
+ const links = linksArg ? (0, parser_common_1.parseLinks)(linksArg, parserContext) : undefined;
752
+ let mappedImports;
879
753
  if (importsArg) {
880
- resolvedImports = parseImports(importsArg, parserContext);
754
+ mappedImports = (0, parser_common_1.parseImports)(importsArg, parserContext);
881
755
  }
882
756
  else {
883
757
  // If no template function was provided, construct one and add the import
@@ -885,7 +759,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
885
759
  let imports = render?.imports
886
760
  ? render.imports
887
761
  : metadata !== undefined
888
- ? getImportsForIdentifiers(parserContext, [metadata.component])
762
+ ? getSourceFilesOfImportedIdentifiers(parserContext, [metadata.component])
889
763
  : [];
890
764
  if (imports.length === 0 && metadata?.component) {
891
765
  // If no imports were found, it might mean that the component is not imported, or
@@ -899,18 +773,18 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
899
773
  },
900
774
  ];
901
775
  }
902
- resolvedImports =
776
+ mappedImports =
903
777
  imports.map((imp) => {
904
778
  if (config) {
905
- const resolvedPath = (0, project_1.resolveImportPath)(imp.file, config);
906
- if (resolvedPath) {
907
- return imp.statement.replace(/['"]([\.\/a-zA-Z0-9]*)['"]/, `'${resolvedPath}'`);
779
+ const mappedPath = (0, project_1.mapImportPath)(imp.file, config);
780
+ if (mappedPath) {
781
+ return imp.statement.replace(/['"]([\.\/a-zA-Z0-9_-]*)['"]/, `'${mappedPath}'`);
908
782
  }
909
783
  }
910
784
  return imp.statement;
911
785
  }) ?? [];
912
786
  }
913
- if (resolvedImports.length === 0 && metadata?.component) {
787
+ if (mappedImports.length === 0 && metadata?.component) {
914
788
  logging_1.logger.warn(`The import statement for ${metadata.component} could not be automatically resolved, make sure the component is imported (if not colocating) and that the path mappings are correct in your figma.config.json`);
915
789
  }
916
790
  let template;
@@ -921,7 +795,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
921
795
  template = getDefaultTemplate(metadata);
922
796
  }
923
797
  else {
924
- throw new ParserError(`${intrinsics_1.FIGMA_CONNECT_CALL}() requires either a component argument or an example function`, { sourceFile, node });
798
+ throw new parser_common_1.ParserError(`${intrinsics_1.FIGMA_CONNECT_CALL}() requires either a component argument or an example function`, { sourceFile, node });
925
799
  }
926
800
  return {
927
801
  figmaNode,
@@ -936,7 +810,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
936
810
  // TODO: `props` here is currently only used for validation purposes,
937
811
  // we should eventually remove it from the JSON payload
938
812
  props,
939
- imports: resolvedImports,
813
+ imports: mappedImports,
940
814
  // If there's no render function, the default example is always nestable
941
815
  nestable: render ? render.nestable : true,
942
816
  },
@@ -946,36 +820,5 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
946
820
  },
947
821
  };
948
822
  }
949
- async function parse(program, file, config, absPath, parseOptions = {}) {
950
- const sourceFile = program.getSourceFile(file);
951
- if (!sourceFile) {
952
- throw new InternalError(`Could not find source file for ${file}`);
953
- }
954
- const parserContext = {
955
- checker: program.getTypeChecker(),
956
- sourceFile,
957
- config,
958
- absPath,
959
- };
960
- const codeConnectObjects = [];
961
- const nodes = [parserContext.sourceFile];
962
- while (nodes.length > 0) {
963
- const node = nodes.shift();
964
- if (isFigmaConnectCall(node, parserContext.sourceFile)) {
965
- const doc = await parseDoc(node, parserContext, parseOptions);
966
- if (doc) {
967
- codeConnectObjects.push(doc);
968
- }
969
- }
970
- nodes.push(...node.getChildren(parserContext.sourceFile));
971
- }
972
- if (codeConnectObjects.length === 0) {
973
- throw new ParserError(`Didn't find any calls to figma.connect()`, {
974
- sourceFile: parserContext.sourceFile,
975
- node: parserContext.sourceFile,
976
- });
977
- }
978
- return codeConnectObjects;
979
- }
980
- exports.parse = parse;
823
+ exports.parseReactDoc = parseReactDoc;
981
824
  //# sourceMappingURL=parser.js.map