Skip to content

Commit c993a47

Browse files
committed
Initial implementation of convertInputSelectorsToArray codemod
1 parent 88e7ffd commit c993a47

23 files changed

+5728
-0
lines changed

codemods/.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Dependencies
2+
/node_modules
3+
4+
# Production
5+
/build
6+
7+
# Generated files
8+
.docusaurus
9+
.cache-loader
10+
11+
# Misc
12+
.DS_Store
13+
.env.local
14+
.env.development.local
15+
.env.test.local
16+
.env.production.local
17+
18+
npm-debug.log*
19+
yarn-debug.log*
20+
yarn-error.log*
21+
.cache
22+
.yarnrc
23+
.yarn/*
24+
!.yarn/patches
25+
!.yarn/releases
26+
!.yarn/plugins
27+
!.yarn/sdks
28+
!.yarn/versions
29+
.pnp.*
30+
*.tgz
31+
32+
tsconfig.vitest-temp.json
33+
.eslintcache
34+
35+
.yalc
36+
.yalc.lock
37+
.vscode/

codemods/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**/__testfixtures__/

codemods/.release-it.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"hooks": {
3+
"after:bump": "yarn && git add -u"
4+
},
5+
"git": {
6+
"commitMessage": "Release reselect-codemods ${version}",
7+
"tagName": "reselect-codemods@${version}"
8+
}
9+
}

codemods/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Reselect Codemods
2+
3+
A collection of codemods for updating legacy Reselect API usage patterns to modern patterns.
4+
5+
## Usage
6+
7+
To run a specific codemod from this project, you would run the following:
8+
9+
```bash
10+
npx reselect-codemods <TRANSFORM NAME> path/of/files/ or/some**/*glob.js
11+
12+
# or
13+
14+
yarn global add reselect-codemods
15+
reselect-codemods <TRANSFORM NAME> path/of/files/ or/some**/*glob.js
16+
```
17+
18+
## Local Usage
19+
20+
```
21+
node ./bin/cli.mjs <TRANSFORM NAME> path/of/files/ or/some**/*glob.js
22+
```
23+
24+
## Transforms
25+
26+
<!--TRANSFORMS_START-->
27+
28+
- [convertInputSelectorsToArray](transforms/convertInputSelectorsToArray/README.md)
29+
30+
<!--TRANSFORMS_END-->
31+
32+
## Contributing
33+
34+
### Installation
35+
36+
- clone the repo
37+
- change into the repo directory
38+
- `yarn`
39+
40+
### Running tests
41+
42+
- `yarn test`

codemods/bin/cli.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env node
2+
import { execaSync } from 'execa'
3+
import { globbySync } from 'globby'
4+
import { createRequire } from 'node:module'
5+
import path from 'node:path'
6+
import { fileURLToPath } from 'node:url'
7+
8+
const require = createRequire(import.meta.url)
9+
10+
const __filename = fileURLToPath(import.meta.url)
11+
const __dirname = path.dirname(__filename)
12+
13+
const transformerDirectory = path.join(
14+
__dirname,
15+
'..',
16+
'transforms',
17+
`${process.argv[2]}/index.ts`,
18+
)
19+
20+
const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
21+
22+
const extensions = 'ts,js,tsx,jsx'
23+
24+
execaSync(
25+
jscodeshiftExecutable,
26+
[
27+
'-t',
28+
transformerDirectory,
29+
'--extensions',
30+
extensions,
31+
...(process.argv.slice(3).length === 1
32+
? globbySync(process.argv[3])
33+
: globbySync(process.argv.slice(3))),
34+
],
35+
{
36+
stdio: 'inherit',
37+
stripFinalNewline: false,
38+
},
39+
)

codemods/eslint.config.mts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import js from '@eslint/js'
2+
import prettierConfig from 'eslint-config-prettier'
3+
import { config, configs, parser } from 'typescript-eslint'
4+
5+
const ESLintConfig = config(
6+
{ name: 'ignores', ignores: ['**/dist/', '**/__testfixtures__/'] },
7+
{ name: 'javascript', ...js.configs.recommended },
8+
...configs.recommended,
9+
...configs.stylistic,
10+
{ name: 'prettier-config', ...prettierConfig },
11+
{
12+
name: 'main',
13+
languageOptions: {
14+
parser,
15+
parserOptions: {
16+
projectService: {
17+
defaultProject: './tsconfig.json',
18+
},
19+
ecmaVersion: 'latest',
20+
},
21+
},
22+
rules: {
23+
'no-undef': [0],
24+
'@typescript-eslint/consistent-type-imports': [
25+
2,
26+
{ fixStyle: 'separate-type-imports', disallowTypeAnnotations: false },
27+
],
28+
'@typescript-eslint/consistent-type-exports': [2],
29+
'@typescript-eslint/no-unused-vars': [0],
30+
'@typescript-eslint/no-explicit-any': [0],
31+
'@typescript-eslint/no-empty-object-type': [
32+
2,
33+
{ allowInterfaces: 'with-single-extends' },
34+
],
35+
'@typescript-eslint/no-namespace': [
36+
2,
37+
{ allowDeclarations: true, allowDefinitionFiles: true },
38+
],
39+
'@typescript-eslint/ban-ts-comment': [0],
40+
'sort-imports': [
41+
2,
42+
{
43+
ignoreCase: false,
44+
ignoreDeclarationSort: true,
45+
ignoreMemberSort: false,
46+
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
47+
allowSeparatedGroups: true,
48+
},
49+
],
50+
},
51+
linterOptions: { reportUnusedDisableDirectives: 2 },
52+
},
53+
{
54+
name: 'commonjs',
55+
files: ['**/*.c[jt]s'],
56+
languageOptions: { sourceType: 'commonjs' },
57+
rules: {
58+
'@typescript-eslint/no-require-imports': [0],
59+
},
60+
},
61+
)
62+
63+
export default ESLintConfig

codemods/package.json

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "reselect-codemods",
3+
"version": "0.1.0",
4+
"scripts": {
5+
"format": "prettier -w . --config prettier.config.mjs",
6+
"format:check": "prettier -c . --config prettier.config.mjs",
7+
"lint": "eslint --flag unstable_ts_config -c eslint.config.mts",
8+
"lint:fix": "eslint --fix --flag unstable_ts_config -c eslint.config.mts",
9+
"test": "vitest --run",
10+
"test:watch": "vitest --watch"
11+
},
12+
"bin": "./bin/cli.mjs",
13+
"files": [
14+
"bin/*",
15+
"transforms/**/index.ts",
16+
"transforms/**/README.md",
17+
"package.json",
18+
"README.md"
19+
],
20+
"keywords": [
21+
"redux",
22+
"redux-toolkit",
23+
"reselect",
24+
"codemod"
25+
],
26+
"dependencies": {
27+
"execa": "^9.3.1",
28+
"globby": "^14.0.2",
29+
"jscodeshift": "^17.0.0",
30+
"ts-node": "^10.9.2",
31+
"typescript": "^5.5.4"
32+
},
33+
"devDependencies": {
34+
"@types/eslint": "^9.6.1",
35+
"@types/eslint-config-prettier": "^6.11.3",
36+
"@types/eslint__js": "^8.42.3",
37+
"@types/jscodeshift": "^0.11.11",
38+
"@types/node": "^22.5.0",
39+
"eslint": "^9.9.1",
40+
"eslint-config-prettier": "^9.1.0",
41+
"jiti": "^2.0.0-beta.3",
42+
"prettier": "^3.3.3",
43+
"typescript-eslint": "^8.3.0",
44+
"vitest": "^2.0.5"
45+
},
46+
"engines": {
47+
"node": ">= 16"
48+
},
49+
"publishConfig": {
50+
"access": "public"
51+
},
52+
"repository": {
53+
"directory": "codemods",
54+
"type": "git",
55+
"url": "git+https://github.com/reduxjs/reselect.git"
56+
},
57+
"sideEffects": false
58+
}

codemods/prettier.config.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @satisfies {import('prettier').Config}
3+
*/
4+
const prettierConfig = {
5+
arrowParens: 'avoid',
6+
semi: false,
7+
singleQuote: true,
8+
}
9+
10+
export default prettierConfig

codemods/transformTestUtils.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { globbySync } from 'globby'
2+
import type { Transform } from 'jscodeshift'
3+
import type { TestOptions } from 'jscodeshift/src/testUtils'
4+
import { applyTransform } from 'jscodeshift/src/testUtils'
5+
import fs from 'node:fs'
6+
import path from 'node:path'
7+
8+
export const runTransformTest = (
9+
name: string,
10+
transform: Transform,
11+
parser: TestOptions['parser'],
12+
fixturePath: string,
13+
) => {
14+
describe(name, () => {
15+
globbySync('**/*.input.*', {
16+
cwd: fixturePath,
17+
absolute: true,
18+
objectMode: true,
19+
})
20+
.map(entry => entry.name)
21+
.forEach(filename => {
22+
const extension = path.extname(filename)
23+
24+
const testName = filename.replace(`.input${extension}`, '')
25+
26+
const testInputPath = path.join(fixturePath, `${testName}${extension}`)
27+
28+
const inputPath = path.join(
29+
fixturePath,
30+
`${testName}.input${extension}`,
31+
)
32+
33+
const outputPath = path.join(
34+
fixturePath,
35+
`${testName}.output${extension}`,
36+
)
37+
38+
const inputFileContent = fs.readFileSync(inputPath, 'utf8')
39+
40+
const expectedOutput = fs.readFileSync(outputPath, 'utf8')
41+
42+
describe(`${testName}${extension}`, () => {
43+
it('transforms correctly', () => {
44+
const output = applyTransform(
45+
transform,
46+
{},
47+
{
48+
path: testInputPath,
49+
source: inputFileContent,
50+
},
51+
{ parser },
52+
)
53+
expect(output).toBe(expectedOutput.trim().replace('\r\n', '\n'))
54+
})
55+
56+
it('is idempotent', () => {
57+
const output = applyTransform(
58+
transform,
59+
{},
60+
{
61+
path: testInputPath,
62+
source: inputFileContent,
63+
},
64+
{ parser },
65+
)
66+
expect(output).toBe(expectedOutput.trim().replace('\r\n', '\n'))
67+
})
68+
})
69+
})
70+
})
71+
}

0 commit comments

Comments
 (0)