@expo/entity-codemod 0.55.0 → 0.57.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.
@@ -0,0 +1,2 @@
1
+ import { API, FileInfo, Options } from 'jscodeshift';
2
+ export default function transformer(file: FileInfo, api: API, _options: Options): string;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = transformer;
4
+ const KNEX_SPECIFIC_METHODS = [
5
+ 'loadFirstByFieldEqualityConjunctionAsync',
6
+ 'loadManyByFieldEqualityConjunctionAsync',
7
+ 'loadManyByRawWhereClauseAsync',
8
+ ];
9
+ function isKnexSpecificMethodUsed(j, node) {
10
+ // Check if this loader call is followed by a knex-specific method
11
+ // We need to traverse the AST to find usages of the loader result
12
+ const parent = node.parent;
13
+ // Check if the loader call is directly chained with a knex method
14
+ if (parent?.value.type === 'MemberExpression' && parent.value.object === node.value) {
15
+ const grandParent = parent.parent;
16
+ if (grandParent?.value.type === 'CallExpression' && grandParent.value.callee === parent.value) {
17
+ if (parent.value.property.type === 'Identifier' &&
18
+ KNEX_SPECIFIC_METHODS.includes(parent.value.property.name)) {
19
+ return true;
20
+ }
21
+ }
22
+ }
23
+ // Check if the loader is assigned to a variable and then used with knex methods
24
+ if (parent?.value.type === 'VariableDeclarator' && parent.value.init === node.value) {
25
+ const variableName = parent.value.id.name;
26
+ const scope = parent.scope;
27
+ // Find all references to this variable in the same scope
28
+ const references = j(scope.path)
29
+ .find(j.Identifier, { name: variableName })
30
+ .filter((path) => {
31
+ // Check if this identifier is used as object in member expression
32
+ const parentNode = path.parent.value;
33
+ if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
34
+ const prop = parentNode.property;
35
+ if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
36
+ return true;
37
+ }
38
+ }
39
+ return false;
40
+ });
41
+ if (references.size() > 0) {
42
+ return true;
43
+ }
44
+ }
45
+ // Check await expressions
46
+ if (parent?.value.type === 'AwaitExpression' && parent.value.argument === node.value) {
47
+ const awaitParent = parent.parent;
48
+ if (awaitParent?.value.type === 'VariableDeclarator') {
49
+ const variableName = awaitParent.value.id.name;
50
+ const scope = awaitParent.scope;
51
+ // Find all references to this variable in the same scope
52
+ const references = j(scope.path)
53
+ .find(j.Identifier, { name: variableName })
54
+ .filter((path) => {
55
+ const parentNode = path.parent.value;
56
+ if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
57
+ const prop = parentNode.property;
58
+ if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ });
64
+ if (references.size() > 0) {
65
+ return true;
66
+ }
67
+ }
68
+ }
69
+ return false;
70
+ }
71
+ function transformLoaderToKnexLoader(j, root) {
72
+ // Find all entity expressions of the form `Entity.loader(viewerContext)`
73
+ root
74
+ .find(j.CallExpression, {
75
+ callee: {
76
+ type: 'MemberExpression',
77
+ property: {
78
+ name: 'loader',
79
+ },
80
+ },
81
+ })
82
+ .forEach((path) => {
83
+ const loaderCallExpression = path.node; // Entity.loader(viewerContext)
84
+ const loaderCallee = loaderCallExpression.callee; // Entity.loader
85
+ if (loaderCallee.type !== 'MemberExpression') {
86
+ return;
87
+ }
88
+ // Make sure this is a static method call on an entity (not on an instance)
89
+ // Typically entity names start with uppercase letter
90
+ if (loaderCallee.object.type === 'Identifier') {
91
+ const firstChar = loaderCallee.object.name[0];
92
+ if (firstChar === firstChar?.toUpperCase()) {
93
+ // Check if this loader uses knex-specific methods
94
+ if (isKnexSpecificMethodUsed(j, path)) {
95
+ // Rename loader to knexLoader
96
+ if (loaderCallee.property.type === 'Identifier') {
97
+ loaderCallee.property.name = 'knexLoader';
98
+ }
99
+ }
100
+ }
101
+ }
102
+ });
103
+ }
104
+ function transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(j, root) {
105
+ // Find all entity expressions of the form `Entity.loaderWithAuthorizationResults(viewerContext)`
106
+ root
107
+ .find(j.CallExpression, {
108
+ callee: {
109
+ type: 'MemberExpression',
110
+ property: {
111
+ name: 'loaderWithAuthorizationResults',
112
+ },
113
+ },
114
+ })
115
+ .forEach((path) => {
116
+ const loaderCallExpression = path.node; // Entity.loaderWithAuthorizationResults(viewerContext)
117
+ const loaderCallee = loaderCallExpression.callee; // Entity.loaderWithAuthorizationResults
118
+ if (loaderCallee.type !== 'MemberExpression') {
119
+ return;
120
+ }
121
+ // Make sure this is a static method call on an entity (not on an instance)
122
+ // Typically entity names start with uppercase letter
123
+ if (loaderCallee.object.type === 'Identifier') {
124
+ const firstChar = loaderCallee.object.name[0];
125
+ if (firstChar === firstChar?.toUpperCase()) {
126
+ // Check if this loader uses knex-specific methods
127
+ if (isKnexSpecificMethodUsed(j, path)) {
128
+ // Rename loaderWithAuthorizationResults to knexLoaderWithAuthorizationResults
129
+ if (loaderCallee.property.type === 'Identifier') {
130
+ loaderCallee.property.name = 'knexLoaderWithAuthorizationResults';
131
+ }
132
+ }
133
+ }
134
+ }
135
+ });
136
+ }
137
+ function transformer(file, api, _options) {
138
+ const j = api.jscodeshift;
139
+ const root = j.withParser('ts')(file.source);
140
+ transformLoaderToKnexLoader(j, root);
141
+ transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(j, root);
142
+ return root.toSource();
143
+ }
144
+ //# sourceMappingURL=v0.55.0-v0.56.0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v0.55.0-v0.56.0.js","sourceRoot":"","sources":["../../../src/transforms/v0.55.0-v0.56.0.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/entity-codemod",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "description": "jscodeshift codemods for @expo/entity upgrades",
5
5
  "files": [
6
6
  "build",
@@ -29,9 +29,9 @@
29
29
  "devDependencies": {
30
30
  "@jest/globals": "30.2.0",
31
31
  "@types/jscodeshift": "17.3.0",
32
- "@types/node": "24.10.9",
32
+ "@types/node": "24.10.13",
33
33
  "jscodeshift": "17.3.0",
34
34
  "typescript": "5.9.3"
35
35
  },
36
- "gitHead": "de7548680dbffb722fb6f5c92106392319782fef"
36
+ "gitHead": "e876cfb27bb9b0004d81b40c9067481e3e0c2beb"
37
37
  }
@@ -0,0 +1,37 @@
1
+ import { ViewerContext } from '@expo/entity';
2
+ import { UserEntity } from './entities/UserEntity';
3
+ import { PostEntity } from './entities/PostEntity';
4
+
5
+ async function loadUser(viewerContext: ViewerContext) {
6
+ // Basic loader calls - only transformed when using knex-specific methods
7
+ const userLoader = UserEntity.loader(viewerContext);
8
+ const postLoader = PostEntity.loader(viewerContext);
9
+
10
+ // These use knex-specific methods, so they should be transformed
11
+ const posts = await postLoader.loadManyByFieldEqualityConjunctionAsync([
12
+ { fieldName: 'status', fieldValue: 'published' }
13
+ ]);
14
+ const firstPost = await postLoader.loadFirstByFieldEqualityConjunctionAsync([
15
+ { fieldName: 'id', fieldValue: '123' }
16
+ ]);
17
+
18
+ // Loader with authorization results - only transformed when using knex methods
19
+ const userLoaderWithAuth = UserEntity.loaderWithAuthorizationResults(viewerContext);
20
+ const rawResults = await userLoaderWithAuth.loadManyByRawWhereClauseAsync('age > ?', [18]);
21
+
22
+ // Loader that doesn't use knex methods - should NOT be transformed
23
+ const standardLoader = PostEntity.loader(viewerContext);
24
+ const post = await standardLoader.loadByIDAsync('456');
25
+
26
+ // Should not transform instance methods or other properties
27
+ const user = await userLoader.loadByIDAsync('123');
28
+ const userLoadMethod = user.loader; // This should not be transformed
29
+
30
+ // Should not transform lowercase object methods
31
+ const customLoader = {
32
+ loader: (ctx: any) => ctx,
33
+ };
34
+ customLoader.loader(viewerContext);
35
+
36
+ return user;
37
+ }
@@ -0,0 +1,37 @@
1
+ import { ViewerContext } from '@expo/entity';
2
+ import { UserEntity } from './entities/UserEntity';
3
+ import { PostEntity } from './entities/PostEntity';
4
+
5
+ async function loadUser(viewerContext: ViewerContext) {
6
+ // Basic loader calls - only transformed when using knex-specific methods
7
+ const userLoader = UserEntity.loader(viewerContext);
8
+ const postLoader = PostEntity.knexLoader(viewerContext);
9
+
10
+ // These use knex-specific methods, so they should be transformed
11
+ const posts = await postLoader.loadManyByFieldEqualityConjunctionAsync([
12
+ { fieldName: 'status', fieldValue: 'published' }
13
+ ]);
14
+ const firstPost = await postLoader.loadFirstByFieldEqualityConjunctionAsync([
15
+ { fieldName: 'id', fieldValue: '123' }
16
+ ]);
17
+
18
+ // Loader with authorization results - only transformed when using knex methods
19
+ const userLoaderWithAuth = UserEntity.knexLoaderWithAuthorizationResults(viewerContext);
20
+ const rawResults = await userLoaderWithAuth.loadManyByRawWhereClauseAsync('age > ?', [18]);
21
+
22
+ // Loader that doesn't use knex methods - should NOT be transformed
23
+ const standardLoader = PostEntity.loader(viewerContext);
24
+ const post = await standardLoader.loadByIDAsync('456');
25
+
26
+ // Should not transform instance methods or other properties
27
+ const user = await userLoader.loadByIDAsync('123');
28
+ const userLoadMethod = user.loader; // This should not be transformed
29
+
30
+ // Should not transform lowercase object methods
31
+ const customLoader = {
32
+ loader: (ctx: any) => ctx,
33
+ };
34
+ customLoader.loader(viewerContext);
35
+
36
+ return user;
37
+ }
@@ -0,0 +1,33 @@
1
+ import { ViewerContext } from '@expo/entity';
2
+ import { CommentEntity } from './entities/CommentEntity';
3
+
4
+ // Chained calls
5
+ const loadComments = async (viewerContext: ViewerContext) => {
6
+ // Direct chaining with knex-specific method
7
+ const comments = await CommentEntity.loader(viewerContext)
8
+ .loadManyByFieldEqualityConjunctionAsync([
9
+ { fieldName: 'postId', fieldValue: '123' }
10
+ ]);
11
+
12
+ // Direct chaining with regular method - should NOT be transformed
13
+ const singleComment = await CommentEntity
14
+ .loader(viewerContext)
15
+ .loadByIDAsync('456');
16
+
17
+ // With authorization results and knex method
18
+ const commentsWithAuth = await CommentEntity
19
+ .loaderWithAuthorizationResults(viewerContext)
20
+ .loadManyByRawWhereClauseAsync('postId = ?', ['456']);
21
+
22
+ // Edge cases - these should NOT be transformed
23
+ const anotherEntity = {
24
+ loader: (ctx: any) => ctx, // This is not an entity class
25
+ };
26
+ anotherEntity.loader(viewerContext); // Should NOT be transformed (lowercase object)
27
+
28
+ // Complex chaining with regular method - should NOT be transformed
29
+ return CommentEntity
30
+ .loader(viewerContext)
31
+ .withAuthenticationResults()
32
+ .loadByIDAsync('789');
33
+ };
@@ -0,0 +1,33 @@
1
+ import { ViewerContext } from '@expo/entity';
2
+ import { CommentEntity } from './entities/CommentEntity';
3
+
4
+ // Chained calls
5
+ const loadComments = async (viewerContext: ViewerContext) => {
6
+ // Direct chaining with knex-specific method
7
+ const comments = await CommentEntity.knexLoader(viewerContext)
8
+ .loadManyByFieldEqualityConjunctionAsync([
9
+ { fieldName: 'postId', fieldValue: '123' }
10
+ ]);
11
+
12
+ // Direct chaining with regular method - should NOT be transformed
13
+ const singleComment = await CommentEntity
14
+ .loader(viewerContext)
15
+ .loadByIDAsync('456');
16
+
17
+ // With authorization results and knex method
18
+ const commentsWithAuth = await CommentEntity
19
+ .knexLoaderWithAuthorizationResults(viewerContext)
20
+ .loadManyByRawWhereClauseAsync('postId = ?', ['456']);
21
+
22
+ // Edge cases - these should NOT be transformed
23
+ const anotherEntity = {
24
+ loader: (ctx: any) => ctx, // This is not an entity class
25
+ };
26
+ anotherEntity.loader(viewerContext); // Should NOT be transformed (lowercase object)
27
+
28
+ // Complex chaining with regular method - should NOT be transformed
29
+ return CommentEntity
30
+ .loader(viewerContext)
31
+ .withAuthenticationResults()
32
+ .loadByIDAsync('789');
33
+ };
@@ -0,0 +1,17 @@
1
+ import { jest } from '@jest/globals';
2
+ import { readdirSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ jest.autoMockOff();
6
+ const defineTest = require('jscodeshift/dist/testUtils').defineTest;
7
+
8
+ const fixtureDir = 'v0.55.0-v0.56.0';
9
+ const fixtureDirPath = join(__dirname, '..', '__testfixtures__', fixtureDir);
10
+ const fixtures = readdirSync(fixtureDirPath)
11
+ .filter((file) => file.endsWith('.input.ts'))
12
+ .map((file) => file.replace('.input.ts', ''));
13
+
14
+ for (const fixture of fixtures) {
15
+ const prefix = `${fixtureDir}/${fixture}`;
16
+ defineTest(__dirname, 'v0.55.0-v0.56.0', null, prefix, { parser: 'ts' });
17
+ }
@@ -0,0 +1,165 @@
1
+ import { API, Collection, FileInfo, Options } from 'jscodeshift';
2
+
3
+ const KNEX_SPECIFIC_METHODS = [
4
+ 'loadFirstByFieldEqualityConjunctionAsync',
5
+ 'loadManyByFieldEqualityConjunctionAsync',
6
+ 'loadManyByRawWhereClauseAsync',
7
+ ];
8
+
9
+ function isKnexSpecificMethodUsed(j: API['jscodeshift'], node: any): boolean {
10
+ // Check if this loader call is followed by a knex-specific method
11
+ // We need to traverse the AST to find usages of the loader result
12
+ const parent = node.parent;
13
+
14
+ // Check if the loader call is directly chained with a knex method
15
+ if (parent?.value.type === 'MemberExpression' && parent.value.object === node.value) {
16
+ const grandParent = parent.parent;
17
+ if (grandParent?.value.type === 'CallExpression' && grandParent.value.callee === parent.value) {
18
+ if (
19
+ parent.value.property.type === 'Identifier' &&
20
+ KNEX_SPECIFIC_METHODS.includes(parent.value.property.name)
21
+ ) {
22
+ return true;
23
+ }
24
+ }
25
+ }
26
+
27
+ // Check if the loader is assigned to a variable and then used with knex methods
28
+ if (parent?.value.type === 'VariableDeclarator' && parent.value.init === node.value) {
29
+ const variableName = parent.value.id.name;
30
+ const scope = parent.scope;
31
+
32
+ // Find all references to this variable in the same scope
33
+ const references = j(scope.path)
34
+ .find(j.Identifier, { name: variableName })
35
+ .filter((path) => {
36
+ // Check if this identifier is used as object in member expression
37
+ const parentNode = path.parent.value;
38
+ if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
39
+ const prop = parentNode.property;
40
+ if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ });
46
+
47
+ if (references.size() > 0) {
48
+ return true;
49
+ }
50
+ }
51
+
52
+ // Check await expressions
53
+ if (parent?.value.type === 'AwaitExpression' && parent.value.argument === node.value) {
54
+ const awaitParent = parent.parent;
55
+ if (awaitParent?.value.type === 'VariableDeclarator') {
56
+ const variableName = awaitParent.value.id.name;
57
+ const scope = awaitParent.scope;
58
+
59
+ // Find all references to this variable in the same scope
60
+ const references = j(scope.path)
61
+ .find(j.Identifier, { name: variableName })
62
+ .filter((path) => {
63
+ const parentNode = path.parent.value;
64
+ if (parentNode.type === 'MemberExpression' && parentNode.object === path.value) {
65
+ const prop = parentNode.property;
66
+ if (prop.type === 'Identifier' && KNEX_SPECIFIC_METHODS.includes(prop.name)) {
67
+ return true;
68
+ }
69
+ }
70
+ return false;
71
+ });
72
+
73
+ if (references.size() > 0) {
74
+ return true;
75
+ }
76
+ }
77
+ }
78
+
79
+ return false;
80
+ }
81
+
82
+ function transformLoaderToKnexLoader(j: API['jscodeshift'], root: Collection<any>): void {
83
+ // Find all entity expressions of the form `Entity.loader(viewerContext)`
84
+ root
85
+ .find(j.CallExpression, {
86
+ callee: {
87
+ type: 'MemberExpression',
88
+ property: {
89
+ name: 'loader',
90
+ },
91
+ },
92
+ })
93
+ .forEach((path) => {
94
+ const loaderCallExpression = path.node; // Entity.loader(viewerContext)
95
+ const loaderCallee = loaderCallExpression.callee; // Entity.loader
96
+
97
+ if (loaderCallee.type !== 'MemberExpression') {
98
+ return;
99
+ }
100
+
101
+ // Make sure this is a static method call on an entity (not on an instance)
102
+ // Typically entity names start with uppercase letter
103
+ if (loaderCallee.object.type === 'Identifier') {
104
+ const firstChar = loaderCallee.object.name[0];
105
+ if (firstChar === firstChar?.toUpperCase()) {
106
+ // Check if this loader uses knex-specific methods
107
+ if (isKnexSpecificMethodUsed(j, path)) {
108
+ // Rename loader to knexLoader
109
+ if (loaderCallee.property.type === 'Identifier') {
110
+ loaderCallee.property.name = 'knexLoader';
111
+ }
112
+ }
113
+ }
114
+ }
115
+ });
116
+ }
117
+
118
+ function transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(
119
+ j: API['jscodeshift'],
120
+ root: Collection<any>,
121
+ ): void {
122
+ // Find all entity expressions of the form `Entity.loaderWithAuthorizationResults(viewerContext)`
123
+ root
124
+ .find(j.CallExpression, {
125
+ callee: {
126
+ type: 'MemberExpression',
127
+ property: {
128
+ name: 'loaderWithAuthorizationResults',
129
+ },
130
+ },
131
+ })
132
+ .forEach((path) => {
133
+ const loaderCallExpression = path.node; // Entity.loaderWithAuthorizationResults(viewerContext)
134
+ const loaderCallee = loaderCallExpression.callee; // Entity.loaderWithAuthorizationResults
135
+
136
+ if (loaderCallee.type !== 'MemberExpression') {
137
+ return;
138
+ }
139
+
140
+ // Make sure this is a static method call on an entity (not on an instance)
141
+ // Typically entity names start with uppercase letter
142
+ if (loaderCallee.object.type === 'Identifier') {
143
+ const firstChar = loaderCallee.object.name[0];
144
+ if (firstChar === firstChar?.toUpperCase()) {
145
+ // Check if this loader uses knex-specific methods
146
+ if (isKnexSpecificMethodUsed(j, path)) {
147
+ // Rename loaderWithAuthorizationResults to knexLoaderWithAuthorizationResults
148
+ if (loaderCallee.property.type === 'Identifier') {
149
+ loaderCallee.property.name = 'knexLoaderWithAuthorizationResults';
150
+ }
151
+ }
152
+ }
153
+ }
154
+ });
155
+ }
156
+
157
+ export default function transformer(file: FileInfo, api: API, _options: Options): string {
158
+ const j = api.jscodeshift;
159
+ const root = j.withParser('ts')(file.source);
160
+
161
+ transformLoaderToKnexLoader(j, root);
162
+ transformLoaderWithAuthorizationResultsToKnexLoaderWithAuthorizationResults(j, root);
163
+
164
+ return root.toSource();
165
+ }