@react-native-windows/codegen 0.0.0-canary.2 → 0.0.0-canary.23

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 (44) hide show
  1. package/CHANGELOG.md +213 -8
  2. package/bin.js +0 -0
  3. package/lib-commonjs/Cli.d.ts +7 -0
  4. package/lib-commonjs/Cli.js +199 -0
  5. package/lib-commonjs/Cli.js.map +1 -0
  6. package/lib-commonjs/generators/AliasGen.d.ts +11 -0
  7. package/lib-commonjs/generators/AliasGen.js +72 -0
  8. package/lib-commonjs/generators/AliasGen.js.map +1 -0
  9. package/lib-commonjs/generators/AliasManaging.d.ts +15 -0
  10. package/lib-commonjs/generators/AliasManaging.js +49 -0
  11. package/lib-commonjs/generators/AliasManaging.js.map +1 -0
  12. package/lib-commonjs/generators/GenerateNM2.d.ts +11 -0
  13. package/lib-commonjs/generators/GenerateNM2.js +94 -0
  14. package/lib-commonjs/generators/GenerateNM2.js.map +1 -0
  15. package/lib-commonjs/generators/ObjectTypes.d.ts +8 -0
  16. package/lib-commonjs/generators/ObjectTypes.js +53 -0
  17. package/lib-commonjs/generators/ObjectTypes.js.map +1 -0
  18. package/lib-commonjs/generators/ParamTypes.d.ts +11 -0
  19. package/lib-commonjs/generators/ParamTypes.js +114 -0
  20. package/lib-commonjs/generators/ParamTypes.js.map +1 -0
  21. package/lib-commonjs/generators/ReturnTypes.d.ts +9 -0
  22. package/lib-commonjs/generators/ReturnTypes.js +63 -0
  23. package/lib-commonjs/generators/ReturnTypes.js.map +1 -0
  24. package/lib-commonjs/generators/ValidateConstants.d.ts +8 -0
  25. package/lib-commonjs/generators/ValidateConstants.js +38 -0
  26. package/lib-commonjs/generators/ValidateConstants.js.map +1 -0
  27. package/lib-commonjs/generators/ValidateMethods.d.ts +8 -0
  28. package/lib-commonjs/generators/ValidateMethods.js +70 -0
  29. package/lib-commonjs/generators/ValidateMethods.js.map +1 -0
  30. package/package.json +25 -14
  31. package/src/Cli.ts +130 -34
  32. package/src/generators/AliasGen.ts +105 -0
  33. package/src/generators/AliasManaging.ts +75 -0
  34. package/src/generators/GenerateNM2.ts +62 -296
  35. package/src/generators/ObjectTypes.ts +70 -0
  36. package/src/generators/ParamTypes.ts +220 -0
  37. package/src/generators/ReturnTypes.ts +92 -0
  38. package/src/generators/ValidateConstants.ts +50 -0
  39. package/src/generators/ValidateMethods.ts +135 -0
  40. package/.eslintrc.js +0 -4
  41. package/.vscode/launch.json +0 -23
  42. package/CHANGELOG.json +0 -426
  43. package/jest.config.js +0 -1
  44. package/tsconfig.json +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-windows/codegen",
3
- "version": "0.0.0-canary.2",
3
+ "version": "0.0.0-canary.23",
4
4
  "description": "Generators for react-native-codegen targeting react-native-windows",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/microsoft/react-native-windows",
@@ -15,33 +15,41 @@
15
15
  "watch": "rnw-scripts watch"
16
16
  },
17
17
  "bin": {
18
- "codegen": "./bin.js"
18
+ "react-native-windows-codegen": "./bin.js"
19
19
  },
20
20
  "dependencies": {
21
+ "@react-native-windows/fs": "^1.0.1",
21
22
  "chalk": "^4.1.0",
22
23
  "globby": "^9.2.0",
23
24
  "mustache": "^4.0.1",
24
- "react-native-tscodegen": "0.66.1",
25
+ "react-native-tscodegen": "0.68.4",
25
26
  "source-map-support": "^0.5.19",
26
- "yargs": "^15.4.1"
27
+ "yargs": "^16.2.0"
27
28
  },
28
29
  "devDependencies": {
29
- "@rnw-scripts/eslint-config": "1.1.6",
30
- "@rnw-scripts/jest-unittest-config": "1.1.1",
31
- "@rnw-scripts/just-task": "2.0.2",
32
- "@rnw-scripts/ts-config": "1.1.0",
30
+ "@rnw-scripts/eslint-config": "1.1.10",
31
+ "@rnw-scripts/jest-unittest-config": "1.2.5",
32
+ "@rnw-scripts/just-task": "2.2.2",
33
+ "@rnw-scripts/ts-config": "2.0.1",
33
34
  "@types/chalk": "^2.2.0",
34
35
  "@types/globby": "^9.1.0",
35
36
  "@types/jest": "^26.0.20",
36
37
  "@types/node": "^14.14.22",
37
- "@types/yargs": "15.0.5",
38
+ "@types/yargs": "16.0.0",
38
39
  "babel-jest": "^26.3.0",
39
- "eslint": "7.12.0",
40
- "jest": "^26.5.2",
40
+ "eslint": "^7.32.0",
41
+ "jest": "^26.6.3",
41
42
  "just-scripts": "^1.3.3",
42
- "prettier": "1.19.1",
43
- "typescript": "^3.8.3"
43
+ "prettier": "^2.4.1",
44
+ "typescript": "^4.4.4"
44
45
  },
46
+ "files": [
47
+ "bin.js",
48
+ "CHANGELOG.md",
49
+ "README.md",
50
+ "lib-commonjs",
51
+ "src"
52
+ ],
45
53
  "beachball": {
46
54
  "defaultNpmTag": "canary",
47
55
  "disallowedChangeTypes": [
@@ -50,5 +58,8 @@
50
58
  "patch"
51
59
  ]
52
60
  },
53
- "promoteRelease": true
61
+ "promoteRelease": true,
62
+ "engines": {
63
+ "node": ">= 12.0.0"
64
+ }
54
65
  }
package/src/Cli.ts CHANGED
@@ -5,15 +5,15 @@
5
5
  * @format
6
6
  */
7
7
 
8
- import * as yargs from 'yargs';
9
- import * as path from 'path';
10
- import * as fs from 'fs';
11
- import * as globby from 'globby';
8
+ import yargs from 'yargs';
9
+ import path from 'path';
10
+ import fs from '@react-native-windows/fs';
11
+ import globby from 'globby';
12
12
  import {createNM2Generator} from './generators/GenerateNM2';
13
13
  // @ts-ignore
14
14
  import {parseFile} from 'react-native-tscodegen/lib/rncodegen/src/parsers/flow';
15
15
  // @ts-ignore
16
- import * as schemaValidator from 'react-native-tscodegen/lib/rncodegen/src/schemaValidator';
16
+ import schemaValidator from 'react-native-tscodegen/lib/rncodegen/src/schemaValidator';
17
17
 
18
18
  const argv = yargs.options({
19
19
  file: {
@@ -33,6 +33,11 @@ const argv = yargs.options({
33
33
  describe: 'C++/C# Namespace to put generated native modules in',
34
34
  default: 'MyNamespace',
35
35
  },
36
+ libraryName: {
37
+ type: 'string',
38
+ required: true,
39
+ describe: 'Used for part of the path generated within the codegen dir',
40
+ },
36
41
  }).argv;
37
42
 
38
43
  import {SchemaType} from 'react-native-tscodegen';
@@ -74,20 +79,44 @@ const GENERATORS = {
74
79
  };
75
80
  */
76
81
 
82
+ function normalizeFileMap(
83
+ map: Map<string, string>,
84
+ outputDir: string,
85
+ outMap: Map<string, string>,
86
+ ): void {
87
+ for (const [fileName, contents] of map) {
88
+ const location = path.join(outputDir, fileName);
89
+ outMap.set(path.normalize(location), contents);
90
+ }
91
+ }
92
+
77
93
  function checkFilesForChanges(
78
94
  map: Map<string, string>,
79
95
  outputDir: string,
80
96
  ): boolean {
81
97
  let hasChanges = false;
82
98
 
83
- for (const [contents, fileName] of map) {
84
- const location = path.join(outputDir, fileName);
85
- if (!fs.existsSync(location)) {
99
+ const allExistingFiles = globby
100
+ .sync(`${outputDir}/**`)
101
+ .map((_) => path.normalize(_))
102
+ .sort();
103
+ const allGeneratedFiles = [...map.keys()]
104
+ .map((_) => path.normalize(_))
105
+ .sort();
106
+
107
+ if (
108
+ allExistingFiles.length !== allGeneratedFiles.length ||
109
+ !allExistingFiles.every((val, index) => val === allGeneratedFiles[index])
110
+ )
111
+ return true;
112
+
113
+ for (const [fileName, contents] of map) {
114
+ if (!fs.existsSync(fileName)) {
86
115
  hasChanges = true;
87
116
  continue;
88
117
  }
89
118
 
90
- const currentContents = fs.readFileSync(location, 'utf8');
119
+ const currentContents = fs.readFileSync(fileName, 'utf8');
91
120
  if (currentContents !== contents) {
92
121
  console.error(`- ${fileName} has changed`);
93
122
  hasChanges = true;
@@ -100,20 +129,48 @@ function checkFilesForChanges(
100
129
 
101
130
  function writeMapToFiles(map: Map<string, string>, outputDir: string) {
102
131
  let success = true;
103
- map.forEach((contents: string, fileName: string) => {
132
+
133
+ // This ensures that we delete any generated files from modules that have been deleted
134
+ const allExistingFiles = globby.sync(`${outputDir}/**`);
135
+ allExistingFiles.forEach((existingFile) => {
136
+ if (!map.has(path.normalize(existingFile))) {
137
+ fs.unlinkSync(existingFile);
138
+ }
139
+ });
140
+
141
+ for (const [fileName, contents] of map) {
104
142
  try {
105
- const location = path.join(outputDir, fileName);
106
- fs.mkdirSync(path.dirname(location), {recursive: true});
107
- fs.writeFileSync(location, contents);
143
+ fs.mkdirSync(path.dirname(fileName), {recursive: true});
144
+
145
+ if (fs.existsSync(fileName)) {
146
+ const currentContents = fs.readFileSync(fileName, 'utf8');
147
+ // Don't update the files if there are no changes as this breaks incremental builds
148
+ if (currentContents === contents) {
149
+ continue;
150
+ }
151
+ }
152
+
153
+ fs.writeFileSync(fileName, contents);
108
154
  } catch (error) {
109
155
  success = false;
110
- console.error(`Failed to write ${fileName} to ${outputDir}`, error);
156
+ console.error(`Failed to write ${fileName} to ${fileName}`, error);
111
157
  }
112
- });
158
+ }
113
159
 
114
160
  return success;
115
161
  }
116
162
 
163
+ function parseFlowFile(filename: string): SchemaType {
164
+ try {
165
+ return parseFile(filename);
166
+ } catch (e) {
167
+ if (e instanceof Error) {
168
+ e.message = `(${filename}): ${e.message}`;
169
+ }
170
+ throw e;
171
+ }
172
+ }
173
+
117
174
  function combineSchemas(files: string[]): SchemaType {
118
175
  return files.reduce(
119
176
  (merged, filename) => {
@@ -123,11 +180,8 @@ function combineSchemas(files: string[]): SchemaType {
123
180
  (/export\s+default\s+\(?codegenNativeComponent</.test(contents) ||
124
181
  contents.includes('extends TurboModule'))
125
182
  ) {
126
- const schema = parseFile(filename);
127
-
128
- if (schema && schema.modules) {
129
- merged.modules = {...merged.modules, ...schema.modules};
130
- }
183
+ const schema = parseFlowFile(filename);
184
+ merged.modules = {...merged.modules, ...schema.modules};
131
185
  }
132
186
  return merged;
133
187
  },
@@ -141,26 +195,68 @@ function generate(
141
195
  ): boolean {
142
196
  schemaValidator.validate(schema);
143
197
 
144
- const generatedFiles = [];
145
- /*
146
- for (const name of generators) {
147
- for (const generator of GENERATORS[name]) {
148
- generatedFiles.push(...generator(libraryName, schema, moduleSpecName));
149
- }
150
- }
151
- */
198
+ const componentOutputdir = path.join(
199
+ outputDirectory,
200
+ 'react/components',
201
+ libraryName,
202
+ );
203
+
204
+ const generatedFiles = new Map<string, string>();
205
+
206
+ generatedFiles.set(
207
+ path.join(outputDirectory, '.clang-format'),
208
+ 'DisableFormat: true\nSortIncludes: false',
209
+ );
152
210
 
153
211
  const generateNM2 = createNM2Generator({namespace: argv.namespace});
212
+ const generatorPropsH =
213
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GeneratePropsH').generate;
214
+ const generatorPropsCPP =
215
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GeneratePropsCPP').generate;
216
+ const generatorShadowNodeH =
217
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GenerateShadowNodeH').generate;
218
+ const generatorShadowNodeCPP =
219
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GenerateShadowNodeCPP').generate;
220
+ const generatorComponentDescriptorH =
221
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GenerateComponentDescriptorH').generate;
222
+ const generatorEventEmitterH =
223
+ require('react-native-tscodegen/lib/rncodegen/src/generators/components/GenerateEventEmitterH').generate;
224
+
225
+ normalizeFileMap(
226
+ generateNM2(libraryName, schema, moduleSpecName),
227
+ outputDirectory,
228
+ generatedFiles,
229
+ );
154
230
 
155
- generatedFiles.push(...generateNM2(libraryName, schema, moduleSpecName));
231
+ if (
232
+ Object.keys(schema.modules).some(
233
+ (moduleName) => schema.modules[moduleName].type === 'Component',
234
+ )
235
+ ) {
236
+ const componentGenerators = [
237
+ generatorPropsH,
238
+ generatorPropsCPP,
239
+ generatorShadowNodeH,
240
+ generatorShadowNodeCPP,
241
+ generatorComponentDescriptorH,
242
+ generatorEventEmitterH,
243
+ ];
156
244
 
157
- const filesToUpdate = new Map<string, string>([...generatedFiles]);
245
+ componentGenerators.forEach((generator) => {
246
+ const generated: Map<string, string> = generator(
247
+ libraryName,
248
+ schema,
249
+ moduleSpecName,
250
+ );
251
+ normalizeFileMap(generated, componentOutputdir, generatedFiles);
252
+ });
253
+ }
158
254
 
159
255
  if (test === true) {
160
- return checkFilesForChanges(filesToUpdate, outputDirectory);
256
+ return checkFilesForChanges(generatedFiles, outputDirectory);
161
257
  }
162
258
 
163
- return writeMapToFiles(filesToUpdate, outputDirectory);
259
+ return writeMapToFiles(generatedFiles, outputDirectory);
164
260
  }
165
261
 
166
262
  if ((argv.file && argv.files) || (!argv.file && !argv.files)) {
@@ -170,12 +266,12 @@ if ((argv.file && argv.files) || (!argv.file && !argv.files)) {
170
266
 
171
267
  let schema: SchemaType;
172
268
  if (argv.file) {
173
- schema = parseFile(argv.file);
269
+ schema = parseFlowFile(argv.file);
174
270
  } else {
175
271
  schema = combineSchemas(globby.sync(argv.files as string[]));
176
272
  }
177
273
 
178
- const libraryName = 'libraryName';
274
+ const libraryName = argv.libraryName;
179
275
  const moduleSpecName = 'moduleSpecName';
180
276
  const outputDirectory = 'codegen';
181
277
  generate(
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ * Licensed under the MIT License.
4
+ * @format
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ import {
10
+ NativeModuleBaseTypeAnnotation,
11
+ NativeModuleObjectTypeAnnotation,
12
+ NamedShape,
13
+ Nullable,
14
+ } from 'react-native-tscodegen';
15
+ import {AliasMap, getAliasCppName} from './AliasManaging';
16
+ import {translateField} from './ObjectTypes';
17
+
18
+ function translateObjectBody(
19
+ type: NativeModuleObjectTypeAnnotation,
20
+ aliases: AliasMap,
21
+ baseAliasName: string,
22
+ prefix: string,
23
+ ) {
24
+ return type.properties
25
+ .map((prop: NamedShape<Nullable<NativeModuleBaseTypeAnnotation>>) => {
26
+ let propType = prop.typeAnnotation;
27
+ if (prop.optional && propType.type !== 'NullableTypeAnnotation') {
28
+ propType = {type: 'NullableTypeAnnotation', typeAnnotation: propType};
29
+ }
30
+ const first = `${prefix}REACT_FIELD(${prop.name})`;
31
+ const second = `${prefix}${translateField(
32
+ propType,
33
+ aliases,
34
+ `${baseAliasName}_${prop.name}`,
35
+ )} ${prop.name};`;
36
+ return `${first}\n${second}`;
37
+ })
38
+ .join('\n');
39
+ }
40
+
41
+ export function createAliasMap(nativeModuleAliases: {
42
+ [name: string]: NativeModuleObjectTypeAnnotation;
43
+ }): AliasMap {
44
+ const aliases: AliasMap = {types: {}, jobs: Object.keys(nativeModuleAliases)};
45
+ for (const aliasName of aliases.jobs) {
46
+ aliases.types[aliasName] = nativeModuleAliases[aliasName];
47
+ }
48
+ return aliases;
49
+ }
50
+
51
+ interface AliasCodeMap {
52
+ [name: string]: string;
53
+ }
54
+
55
+ function generateSingleAlias(
56
+ aliases: AliasMap,
57
+ aliasName: string,
58
+ aliasCode: AliasCodeMap,
59
+ ): void {
60
+ const aliasType = <NativeModuleObjectTypeAnnotation>aliases.types[aliasName];
61
+ aliasCode[aliasName] = `
62
+ REACT_STRUCT(${getAliasCppName(aliasName)})
63
+ struct ${getAliasCppName(aliasName)} {
64
+ ${translateObjectBody(aliasType, aliases, aliasName, ' ')}
65
+ };
66
+ `;
67
+ }
68
+
69
+ function generateNestedAliasesInCorrectOrder(
70
+ aliases: AliasMap,
71
+ aliasCode: AliasCodeMap,
72
+ aliasOrder: string[],
73
+ ): void {
74
+ // retrieve and clean all ungenerated aliases
75
+ const jobs = aliases.jobs;
76
+ aliases.jobs = [];
77
+
78
+ // generate each one in its found order
79
+ for (const aliasName of jobs) {
80
+ // generate a new struct and all fields will be examined
81
+ // new anonymous objects could be found
82
+ // they will be stored in aliases.jobs
83
+ generateSingleAlias(aliases, aliasName, aliasCode);
84
+ // nested C++ structs must be put before the current C++ struct
85
+ // as they will be used in the current C++ struct
86
+ // the order will be perfectly and easily ensured by doing this recursively
87
+ generateNestedAliasesInCorrectOrder(aliases, aliasCode, aliasOrder);
88
+ // all referenced C++ structs are generated
89
+ // put the current one following them
90
+ aliasOrder.push(aliasName);
91
+ }
92
+ }
93
+
94
+ export function generateAliases(aliases: AliasMap): string {
95
+ const aliasCode: AliasCodeMap = {};
96
+ const aliasOrder: string[] = [];
97
+ generateNestedAliasesInCorrectOrder(aliases, aliasCode, aliasOrder);
98
+
99
+ // aliasOrder now has the correct order of C++ struct code
100
+ let traversedAliasedStructs = '';
101
+ for (const aliasName of aliasOrder) {
102
+ traversedAliasedStructs = `${traversedAliasedStructs}${aliasCode[aliasName]}`;
103
+ }
104
+ return traversedAliasedStructs;
105
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Copyright (c) Microsoft Corporation.
3
+ * Licensed under the MIT License.
4
+ * @format
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ import {NativeModuleObjectTypeAnnotation} from 'react-native-tscodegen';
10
+
11
+ let preferredModuleName: string = '';
12
+
13
+ export function setPreferredModuleName(moduleName: string): void {
14
+ preferredModuleName = moduleName;
15
+ }
16
+
17
+ export function getAliasCppName(typeName: string): string {
18
+ return `${preferredModuleName}Spec_${typeName}`;
19
+ }
20
+
21
+ export interface AliasMap {
22
+ types: {[name: string]: NativeModuleObjectTypeAnnotation | undefined};
23
+ jobs: string[];
24
+ }
25
+
26
+ const ExtendedObjectKey = '$RNW-TURBOMODULE-ALIAS';
27
+ interface ExtendedObject extends NativeModuleObjectTypeAnnotation {
28
+ '$RNW-TURBOMODULE-ALIAS'?: string;
29
+ }
30
+
31
+ function recordAnonymouseAlias(
32
+ aliases: AliasMap,
33
+ baseAliasName: string,
34
+ extended: ExtendedObject,
35
+ ): string {
36
+ extended[ExtendedObjectKey] = baseAliasName;
37
+ aliases.types[baseAliasName] = extended;
38
+ aliases.jobs.push(baseAliasName);
39
+ return baseAliasName;
40
+ }
41
+
42
+ export function getAnonymousAliasCppName(
43
+ aliases: AliasMap,
44
+ baseAliasName: string,
45
+ objectType: NativeModuleObjectTypeAnnotation,
46
+ ): string {
47
+ // someone found an anonymous object literal type
48
+ // if the ExtendedObjectKey flag has been set
49
+ // then it is a known one
50
+ // this happens because method signatures are generate twice in spec and error messages
51
+ const extended = <ExtendedObject>objectType;
52
+ const key = extended[ExtendedObjectKey];
53
+ if (key !== undefined) {
54
+ return getAliasCppName(key);
55
+ }
56
+
57
+ // if the ExtendedObjectKey flag has not been set
58
+ // it means it is a unknown one
59
+ // associate the name with this object literal type and return
60
+ if (aliases.types[baseAliasName] === undefined) {
61
+ return getAliasCppName(
62
+ recordAnonymouseAlias(aliases, baseAliasName, extended),
63
+ );
64
+ }
65
+
66
+ // sometimes names could be anonymous
67
+ let index = 2;
68
+ while (aliases.types[`${baseAliasName}${index}`] !== undefined) {
69
+ index++;
70
+ }
71
+
72
+ return getAliasCppName(
73
+ recordAnonymouseAlias(aliases, `${baseAliasName}${index}`, extended),
74
+ );
75
+ }