@figma/code-connect 1.0.6 → 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 (156) hide show
  1. package/README.md +2 -595
  2. package/dist/client/figma_client.d.ts +1 -1
  3. package/dist/client/figma_client.js +9 -7
  4. package/dist/client/figma_client.js.map +1 -1
  5. package/dist/commands/connect.d.ts +4 -2
  6. package/dist/commands/connect.d.ts.map +1 -1
  7. package/dist/commands/connect.js +97 -49
  8. package/dist/commands/connect.js.map +1 -1
  9. package/dist/common/project.d.ts +86 -0
  10. package/dist/common/project.d.ts.map +1 -0
  11. package/dist/common/project.js +138 -0
  12. package/dist/common/project.js.map +1 -0
  13. package/dist/connect/api.d.ts +143 -0
  14. package/dist/connect/api.d.ts.map +1 -0
  15. package/dist/connect/api.js +3 -0
  16. package/dist/connect/api.js.map +1 -0
  17. package/dist/connect/create.d.ts.map +1 -1
  18. package/dist/connect/create.js +4 -0
  19. package/dist/connect/create.js.map +1 -1
  20. package/dist/connect/create_common.d.ts +7 -0
  21. package/dist/connect/create_common.d.ts.map +1 -0
  22. package/dist/connect/create_common.js +19 -0
  23. package/dist/connect/create_common.js.map +1 -0
  24. package/dist/connect/external_types.d.ts +8 -0
  25. package/dist/connect/external_types.d.ts.map +1 -0
  26. package/dist/connect/external_types.js +31 -0
  27. package/dist/connect/external_types.js.map +1 -0
  28. package/dist/connect/figma_connect.d.ts +26 -0
  29. package/dist/connect/figma_connect.d.ts.map +1 -0
  30. package/dist/connect/figma_connect.js +3 -0
  31. package/dist/connect/figma_connect.js.map +1 -0
  32. package/dist/connect/figma_rest_api.d.ts +7 -0
  33. package/dist/connect/figma_rest_api.d.ts.map +1 -1
  34. package/dist/connect/figma_rest_api.js.map +1 -1
  35. package/dist/connect/index_common.d.ts +3 -0
  36. package/dist/connect/index_common.d.ts.map +1 -0
  37. package/dist/connect/index_common.js +23 -0
  38. package/dist/connect/index_common.js.map +1 -0
  39. package/dist/connect/intrinsics.d.ts +94 -0
  40. package/dist/connect/intrinsics.d.ts.map +1 -0
  41. package/dist/connect/intrinsics.js +401 -0
  42. package/dist/connect/intrinsics.js.map +1 -0
  43. package/dist/connect/parser_common.d.ts +144 -0
  44. package/dist/connect/parser_common.d.ts.map +1 -0
  45. package/dist/connect/parser_common.js +297 -0
  46. package/dist/connect/parser_common.js.map +1 -0
  47. package/dist/connect/parser_executable_types.d.ts +20 -23
  48. package/dist/connect/parser_executable_types.d.ts.map +1 -1
  49. package/dist/connect/parser_executable_types.js.map +1 -1
  50. package/dist/connect/parser_executables.js +1 -1
  51. package/dist/connect/parser_executables.js.map +1 -1
  52. package/dist/connect/project.d.ts +20 -5
  53. package/dist/connect/project.d.ts.map +1 -1
  54. package/dist/connect/project.js +79 -16
  55. package/dist/connect/project.js.map +1 -1
  56. package/dist/connect/upload.d.ts +1 -1
  57. package/dist/connect/upload.d.ts.map +1 -1
  58. package/dist/connect/validation.d.ts +1 -1
  59. package/dist/connect/validation.d.ts.map +1 -1
  60. package/dist/connect/wizard/helpers.d.ts.map +1 -1
  61. package/dist/connect/wizard/helpers.js +18 -14
  62. package/dist/connect/wizard/helpers.js.map +1 -1
  63. package/dist/connect/wizard/prop_mapping.d.ts +12 -7
  64. package/dist/connect/wizard/prop_mapping.d.ts.map +1 -1
  65. package/dist/connect/wizard/prop_mapping.js +88 -89
  66. package/dist/connect/wizard/prop_mapping.js.map +1 -1
  67. package/dist/connect/wizard/run_wizard.d.ts.map +1 -1
  68. package/dist/connect/wizard/run_wizard.js +8 -3
  69. package/dist/connect/wizard/run_wizard.js.map +1 -1
  70. package/dist/connect/wizard/signature_extraction.d.ts +7 -0
  71. package/dist/connect/wizard/signature_extraction.d.ts.map +1 -0
  72. package/dist/connect/wizard/signature_extraction.js +46 -0
  73. package/dist/connect/wizard/signature_extraction.js.map +1 -0
  74. package/dist/html/__test__/parser/examples/NoConfigObject.figma.d.ts +2 -0
  75. package/dist/html/__test__/parser/examples/NoConfigObject.figma.d.ts.map +1 -0
  76. package/dist/html/__test__/parser/examples/NoConfigObject.figma.js +9 -0
  77. package/dist/html/__test__/parser/examples/NoConfigObject.figma.js.map +1 -0
  78. package/dist/html/__test__/parser/examples/NoExample.figma.d.ts +2 -0
  79. package/dist/html/__test__/parser/examples/NoExample.figma.d.ts.map +1 -0
  80. package/dist/html/__test__/parser/examples/NoExample.figma.js +9 -0
  81. package/dist/html/__test__/parser/examples/NoExample.figma.js.map +1 -0
  82. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.d.ts +2 -0
  83. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.d.ts.map +1 -0
  84. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.js +33 -0
  85. package/dist/html/__test__/parser/examples/RegularFunctionExampleWithExtraCode.figma.js.map +1 -0
  86. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.d.ts +2 -0
  87. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.d.ts.map +1 -0
  88. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.js +15 -0
  89. package/dist/html/__test__/parser/examples/WrongSignatureJsx.figma.js.map +1 -0
  90. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.d.ts +2 -0
  91. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.d.ts.map +1 -0
  92. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.js +15 -0
  93. package/dist/html/__test__/parser/examples/WrongSignatureNoHtmlTag.figma.js.map +1 -0
  94. package/dist/html/__test__/parser/parser.test.d.ts +2 -0
  95. package/dist/html/__test__/parser/parser.test.d.ts.map +1 -0
  96. package/dist/html/__test__/parser/parser.test.js +50 -0
  97. package/dist/html/__test__/parser/parser.test.js.map +1 -0
  98. package/dist/html/create.d.ts +4 -0
  99. package/dist/html/create.d.ts.map +1 -0
  100. package/dist/html/create.js +81 -0
  101. package/dist/html/create.js.map +1 -0
  102. package/dist/html/external.d.ts +8 -0
  103. package/dist/html/external.d.ts.map +1 -0
  104. package/dist/html/external.js +25 -0
  105. package/dist/html/external.js.map +1 -0
  106. package/dist/html/index_html.d.ts +77 -0
  107. package/dist/html/index_html.d.ts.map +1 -0
  108. package/dist/html/index_html.js +42 -0
  109. package/dist/html/index_html.js.map +1 -0
  110. package/dist/html/parser.d.ts +19 -0
  111. package/dist/html/parser.d.ts.map +1 -0
  112. package/dist/html/parser.js +487 -0
  113. package/dist/html/parser.js.map +1 -0
  114. package/dist/html/parser_template_helpers.d.ts +2 -0
  115. package/dist/html/parser_template_helpers.d.ts.map +1 -0
  116. package/dist/html/parser_template_helpers.js +68 -0
  117. package/dist/html/parser_template_helpers.js.map +1 -0
  118. package/dist/html/template_literal.d.ts +5 -0
  119. package/dist/html/template_literal.d.ts.map +1 -0
  120. package/dist/html/template_literal.js +8 -0
  121. package/dist/html/template_literal.js.map +1 -0
  122. package/dist/parser_scripts/get_swift_parser_dir.d.ts +1 -1
  123. package/dist/parser_scripts/get_swift_parser_dir.d.ts.map +1 -1
  124. package/dist/parser_scripts/get_swift_parser_dir.js +38 -10
  125. package/dist/parser_scripts/get_swift_parser_dir.js.map +1 -1
  126. package/dist/react/create.d.ts +5 -0
  127. package/dist/react/create.d.ts.map +1 -1
  128. package/dist/react/create.js +144 -42
  129. package/dist/react/create.js.map +1 -1
  130. package/dist/react/external.d.ts +9 -0
  131. package/dist/react/external.d.ts.map +1 -0
  132. package/dist/react/external.js +45 -0
  133. package/dist/react/external.js.map +1 -0
  134. package/dist/react/index_react.d.ts +94 -0
  135. package/dist/react/index_react.d.ts.map +1 -0
  136. package/dist/react/index_react.js +39 -0
  137. package/dist/react/index_react.js.map +1 -0
  138. package/dist/react/parser.d.ts +6 -38
  139. package/dist/react/parser.d.ts.map +1 -1
  140. package/dist/react/parser.js +137 -295
  141. package/dist/react/parser.js.map +1 -1
  142. package/dist/react/parser_template_helpers.d.ts +15 -15
  143. package/dist/react/parser_template_helpers.d.ts.map +1 -1
  144. package/dist/react/parser_template_helpers.js +51 -35
  145. package/dist/react/parser_template_helpers.js.map +1 -1
  146. package/dist/storybook/convert.d.ts +1 -1
  147. package/dist/storybook/convert.d.ts.map +1 -1
  148. package/dist/storybook/convert.js +6 -4
  149. package/dist/storybook/convert.js.map +1 -1
  150. package/dist/storybook/external.d.ts +1 -1
  151. package/dist/storybook/external.d.ts.map +1 -1
  152. package/dist/typescript/compiler.d.ts +2 -2
  153. package/dist/typescript/compiler.d.ts.map +1 -1
  154. package/dist/typescript/compiler.js +50 -23
  155. package/dist/typescript/compiler.js.map +1 -1
  156. package/package.json +27 -7
@@ -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
@@ -671,41 +564,22 @@ function parseRenderFunction(exp, parserContext, propMappings) {
671
564
  templateCode = (0, parser_template_helpers_1.getParsedTemplateHelpersString)() + '\n\n';
672
565
  // Require the template API
673
566
  templateCode += `const figma = require('figma')\n\n`;
674
- let nestedLayerCount = 0;
675
567
  // Then we output `const propName = figma.properties.<kind>('propName')` calls
676
568
  // for each referenced prop, so these are accessible to the template code.
677
- if (propMappings && referencedProps.size > 0) {
678
- referencedProps.forEach((prop) => {
679
- const propMapping = propMappings[prop];
680
- if (!propMapping) {
681
- throw new ParserError(`Could not find prop mapping for ${prop}`, {
682
- sourceFile,
683
- node: exp,
684
- });
685
- }
686
- if (propMapping.kind === intrinsics_1.IntrinsicKind.NestedProps) {
687
- // the actual layer name in figma could have a bunch of special characters in it,
688
- // and if we try to normalize it to a valid JS identifier, it could conflict with
689
- // other variables in the template code. So we generate a unique variable name
690
- // for each nested layer reference instead.
691
- const nestedLayerRef = `nestedLayer${nestedLayerCount++}`;
692
- templateCode += `const ${nestedLayerRef} = figma.currentLayer.__find__("${propMapping.args.layer}")\n`;
693
- templateCode += `const ${prop} = {
694
- ${Object.entries(propMapping.args.props).map(([key, intrinsic]) => `${key}: ${(0, intrinsics_1.intrinsicToString)(intrinsic, nestedLayerRef)}\n`)}
695
- }\n`;
696
- }
697
- else {
698
- templateCode += `const ${prop} = ${(0, intrinsics_1.intrinsicToString)(propMapping)}\n`;
699
- }
700
- });
701
- templateCode += '\n';
702
- }
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, '\\`');
703
577
  // Finally, output the example code
704
578
  templateCode += `export default figma.tsx\`${exampleCode}\`\n`;
705
579
  // Find all JSX elements in the function body and extract their import
706
580
  // statements
707
- const jsxTags = findDescendants(exp, (element) => typescript_1.default.isJsxElement(element) || typescript_1.default.isJsxSelfClosingElement(element)).map(compiler_1.getTagName);
708
- 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));
709
583
  return {
710
584
  code: templateCode,
711
585
  imports,
@@ -731,7 +605,7 @@ function followIdentifierToStringLiteralDeclaration(node, parserContext, errMess
731
605
  // If we followed the identifier to its declaration and it's not a string literal,
732
606
  // throw an error
733
607
  if (!result || !typescript_1.default.isStringLiteral(result)) {
734
- throw new ParserError(errMessage, {
608
+ throw new parser_common_1.ParserError(errMessage, {
735
609
  node: result,
736
610
  sourceFile: parserContext.sourceFile,
737
611
  });
@@ -855,17 +729,16 @@ function getDefaultTemplate(componentMetadata) {
855
729
  return `const figma = require("figma")\n\nexport default figma.tsx\`${example}\``;
856
730
  }
857
731
  exports.getDefaultTemplate = getDefaultTemplate;
858
- async function parseDoc(node, parserContext, { repoUrl, silent }) {
732
+ async function parseReactDoc(node, parserContext, { repoUrl, silent }) {
859
733
  const { checker, sourceFile, config } = parserContext;
860
734
  // Parse the arguments to the `Figma.connect()` call
861
735
  const { componentArg, figmaNodeUrlArg, configObjArg } = parseFigmaConnectArgs(node, parserContext);
862
- // The ones with ! are definitely defined because their parser fn has required: true,
863
- // but I couldn't work out how to model that in TypeScript
864
736
  const { propsArg, exampleArg, variantArg, linksArg, importsArg } = parseConfigObjectArg(configObjArg, parserContext);
865
- let figmaNode = (0, compiler_1.stripQuotes)(figmaNodeUrlArg);
737
+ let figmaNode = (0, compiler_1.stripQuotesFromNode)(figmaNodeUrlArg);
866
738
  // TODO This logic is duplicated in connect.ts transformDocFromParser due to some type issues
867
739
  if (config.documentUrlSubstitutions) {
868
740
  Object.entries(config.documentUrlSubstitutions).forEach(([from, to]) => {
741
+ // @ts-expect-error
869
742
  figmaNode = figmaNode.replace(from, to);
870
743
  });
871
744
  }
@@ -874,11 +747,11 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
874
747
  : undefined;
875
748
  const props = propsArg ? (0, intrinsics_1.parsePropsObject)(propsArg, parserContext) : undefined;
876
749
  const render = exampleArg ? parseRenderFunction(exampleArg, parserContext, props) : undefined;
877
- const variant = variantArg ? parseVariant(variantArg, sourceFile, checker) : undefined;
878
- const links = linksArg ? parseLinks(linksArg, parserContext) : undefined;
879
- 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;
880
753
  if (importsArg) {
881
- resolvedImports = parseImports(importsArg, parserContext);
754
+ mappedImports = (0, parser_common_1.parseImports)(importsArg, parserContext);
882
755
  }
883
756
  else {
884
757
  // If no template function was provided, construct one and add the import
@@ -886,7 +759,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
886
759
  let imports = render?.imports
887
760
  ? render.imports
888
761
  : metadata !== undefined
889
- ? getImportsForIdentifiers(parserContext, [metadata.component])
762
+ ? getSourceFilesOfImportedIdentifiers(parserContext, [metadata.component])
890
763
  : [];
891
764
  if (imports.length === 0 && metadata?.component) {
892
765
  // If no imports were found, it might mean that the component is not imported, or
@@ -900,18 +773,18 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
900
773
  },
901
774
  ];
902
775
  }
903
- resolvedImports =
776
+ mappedImports =
904
777
  imports.map((imp) => {
905
778
  if (config) {
906
- const resolvedPath = (0, project_1.resolveImportPath)(imp.file, config);
907
- if (resolvedPath) {
908
- 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}'`);
909
782
  }
910
783
  }
911
784
  return imp.statement;
912
785
  }) ?? [];
913
786
  }
914
- if (resolvedImports.length === 0 && metadata?.component) {
787
+ if (mappedImports.length === 0 && metadata?.component) {
915
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`);
916
789
  }
917
790
  let template;
@@ -922,7 +795,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
922
795
  template = getDefaultTemplate(metadata);
923
796
  }
924
797
  else {
925
- 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 });
926
799
  }
927
800
  return {
928
801
  figmaNode,
@@ -937,7 +810,7 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
937
810
  // TODO: `props` here is currently only used for validation purposes,
938
811
  // we should eventually remove it from the JSON payload
939
812
  props,
940
- imports: resolvedImports,
813
+ imports: mappedImports,
941
814
  // If there's no render function, the default example is always nestable
942
815
  nestable: render ? render.nestable : true,
943
816
  },
@@ -947,36 +820,5 @@ async function parseDoc(node, parserContext, { repoUrl, silent }) {
947
820
  },
948
821
  };
949
822
  }
950
- async function parse(program, file, config, absPath, parseOptions = {}) {
951
- const sourceFile = program.getSourceFile(file);
952
- if (!sourceFile) {
953
- throw new InternalError(`Could not find source file for ${file}`);
954
- }
955
- const parserContext = {
956
- checker: program.getTypeChecker(),
957
- sourceFile,
958
- config,
959
- absPath,
960
- };
961
- const codeConnectObjects = [];
962
- const nodes = [parserContext.sourceFile];
963
- while (nodes.length > 0) {
964
- const node = nodes.shift();
965
- if (isFigmaConnectCall(node, parserContext.sourceFile)) {
966
- const doc = await parseDoc(node, parserContext, parseOptions);
967
- if (doc) {
968
- codeConnectObjects.push(doc);
969
- }
970
- }
971
- nodes.push(...node.getChildren(parserContext.sourceFile));
972
- }
973
- if (codeConnectObjects.length === 0) {
974
- throw new ParserError(`Didn't find any calls to figma.connect()`, {
975
- sourceFile: parserContext.sourceFile,
976
- node: parserContext.sourceFile,
977
- });
978
- }
979
- return codeConnectObjects;
980
- }
981
- exports.parse = parse;
823
+ exports.parseReactDoc = parseReactDoc;
982
824
  //# sourceMappingURL=parser.js.map