Skip to content

Commit d55458e

Browse files
chore: add common package (#59)
1 parent d98b7ea commit d55458e

22 files changed

+306
-7
lines changed

.eslintrc.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ module.exports = {
1111
jsx: true,
1212
},
1313
EXPERIMENTAL_useSourceOfProjectReferenceRedirect: true,
14-
project: ['./tsconfig.eslint.json', './tsconfig.json', './packages/*/tsconfig.json'],
15-
allowAutomaticSingleRunInference: true,
14+
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
1615
tsconfigRootDir: __dirname,
1716
warnOnUnsupportedTypeScriptVersion: false,
1817
},

packages/codemod/src/transforms/globalCssToCssModule/ts/splitClassName.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
interface SplitClassNameOptions {
2+
// ClassName value string which might contain replaceable parts.
23
className: string
4+
// Mapping between classes and exportNames.
35
exportNameMap: Record<string, string>
6+
// Object to collect replaced classes usage in React component.
47
usageStats: Record<string, boolean>
58
}
69

10+
// Object that contains array of exportNames found in the className and the left over value.
711
interface SplitClassNameResult {
812
exportNames: string[]
913
leftOverClassnames: string[]
@@ -12,11 +16,6 @@ interface SplitClassNameResult {
1216
/**
1317
* Example: splitClassName({ className: 'kek--wow d-flex mr-1', exportNameMap: { 'kek--wow': 'kekWow' }, usageStats: {})
1418
* returns: { exportNames: ['kekWow'], leftOverClassnames: ['d-flex', 'mr-1'] }
15-
*
16-
* @param className ClassName value string which might contain replaceable parts.
17-
* @param exportNameMap Mapping between classes and exportNames.
18-
* @param usageStats Object to collect replaced classes usage in React component.
19-
* @returns Object that contains array of exportNames found in the className and the left over value.
2019
*/
2120
export function splitClassName(options: SplitClassNameOptions): SplitClassNameResult {
2221
const { className, exportNameMap, usageStats } = options

packages/common/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Common
2+
3+
Contains generic utilities reused between multiple packages of the codemod monorepo.

packages/common/jest.config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { InitialOptionsTsJest } from 'ts-jest'
2+
3+
import baseConfig from '../../jest.config.base'
4+
5+
const config: InitialOptionsTsJest = {
6+
...baseConfig,
7+
displayName: 'codemod-common',
8+
rootDir: __dirname,
9+
}
10+
11+
// eslint-disable-next-line import/no-default-export
12+
export default config

packages/common/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"private": true,
3+
"name": "@sourcegraph/codemod-common",
4+
"version": "1.0.0",
5+
"description": "@sourcegraph/codemod-common",
6+
"license": "Apache-2.0",
7+
"main": "dist/index.js",
8+
"types": "dist/index.d.ts",
9+
"scripts": {
10+
"build": "tsc --build ./tsconfig.build.json",
11+
"build:watch": "tsc --build ./tsconfig.build.json --watch",
12+
"build:clean": "tsc --build --clean ./tsconfig.build.json && rimraf dist ./*.tsbuildinfo",
13+
"typecheck": "tsc --noEmit",
14+
"test": "jest",
15+
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
16+
"lint": "eslint './src/**/*.ts?(x)'"
17+
}
18+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import signale from 'signale'
2+
import { Node } from 'ts-morph'
3+
4+
import { ManualChangeError } from './errors'
5+
6+
export interface ManualChangeLog {
7+
node: Node
8+
message: string
9+
}
10+
11+
export type ManualChangesReported = Record<string, ManualChangeLog>
12+
13+
export interface ManualChangeList {
14+
manualChangesReported: ManualChangesReported
15+
throwManualChangeError(manualChangeLog: ManualChangeLog): void
16+
addManualChangeLog(manualChangeLog: ManualChangeLog): void
17+
}
18+
19+
// Used to avoid the duplicate reports on the same AST node.
20+
function getManualLogId(manualChangeLog: ManualChangeLog): string {
21+
const { node, message } = manualChangeLog
22+
23+
return [
24+
node.getSourceFile().getFilePath(),
25+
node.getPos(),
26+
node.getStartLinePos(),
27+
node.getEndLineNumber(),
28+
message,
29+
].join(',')
30+
}
31+
32+
export function throwManualChangeError({ node, message }: ManualChangeLog): void {
33+
throw new ManualChangeError(node, message)
34+
}
35+
36+
export function logRequiredManualChanges(manualChangesReported: ManualChangesReported): void {
37+
for (const { message } of Object.values(manualChangesReported)) {
38+
signale.log(message)
39+
}
40+
}
41+
42+
export function createManualChangeList(this: void): ManualChangeList {
43+
const manualChangesReported: ManualChangesReported = {}
44+
45+
function addManualChangeLog(manualChangeLog: ManualChangeLog): void {
46+
const { node, message } = manualChangeLog
47+
const { line, column } = node.getSourceFile().getLineAndColumnAtPos(node.getPos())
48+
const filePath = node.getSourceFile().getFilePath()
49+
50+
manualChangesReported[getManualLogId(manualChangeLog)] = {
51+
node,
52+
message: ['', `${filePath}:${line}:${column} - warning: ${message}`, `>>> ${node.getFullText()}`].join(
53+
'\n'
54+
),
55+
}
56+
}
57+
58+
return { manualChangesReported, throwManualChangeError, addManualChangeLog }
59+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { createManualChangeList } from '../ManualChangeList'
2+
import { createSourceFile } from '../testing'
3+
4+
describe('createManualChangeList', () => {
5+
it('collects manual change logs', () => {
6+
const manualChangeList = createManualChangeList()
7+
8+
const { sourceFile } = createSourceFile('const x = 1')
9+
const manualChangeLog = {
10+
node: sourceFile.getFirstChildOrThrow(),
11+
message: 'Hello there!',
12+
}
13+
14+
expect(manualChangeList.manualChangesReported).toEqual({})
15+
16+
manualChangeList.addManualChangeLog(manualChangeLog)
17+
manualChangeList.addManualChangeLog(manualChangeLog)
18+
19+
const changelogEntries = Object.entries(manualChangeList.manualChangesReported)
20+
expect(changelogEntries.length).toBe(1)
21+
22+
const [id, { node, message }] = changelogEntries[0]
23+
24+
expect(node).toBe(manualChangeLog.node)
25+
expect(message).toBe(['', '/test.tsx:1:1 - warning: Hello there!', '>>> const x = 1'].join('\n'))
26+
expect(id).toBe('/test.tsx,0,0,1,Hello there!')
27+
})
28+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { throwFromMethodsIfUndefinedReturn } from '../errors'
2+
3+
describe('throwFromMethodsIfUndefinedReturn', () => {
4+
it('throws from object method if undefined is returned', () => {
5+
const object = {
6+
void() {
7+
// nothing here
8+
},
9+
}
10+
11+
expect(() => {
12+
throwFromMethodsIfUndefinedReturn(object).void()
13+
}).toThrow()
14+
})
15+
16+
it('preserves type signature of methods', () => {
17+
const object = {
18+
void(count: number, name: string) {
19+
if (count && name) {
20+
// nothing here
21+
}
22+
},
23+
}
24+
25+
expect(() => {
26+
throwFromMethodsIfUndefinedReturn(object).void(0, 'name')
27+
}).toThrow()
28+
})
29+
})

packages/common/src/errors.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { errors } from '@ts-morph/common'
2+
import { Node } from 'ts-morph'
3+
4+
import { AnyFunction, NonVoidFunction } from './types'
5+
6+
export { errors }
7+
8+
export class ManualChangeError extends Error {
9+
constructor(public node: Node, public message: string) {
10+
super(message)
11+
}
12+
}
13+
14+
type NonVoidRecordMethods<T extends Record<string, AnyFunction>> = {
15+
[key in keyof T]: NonVoidFunction<T[key]>
16+
}
17+
18+
/**
19+
* Wraps each object's method into `errors.throwIfNullOrUndefined()` and updates type signature accordingly.
20+
*/
21+
export function throwFromMethodsIfUndefinedReturn<T extends Record<string, AnyFunction>>(
22+
record: T
23+
): NonVoidRecordMethods<T> {
24+
const entriesWithWrappedValueCall = Object.entries(record).map(([key, value]) => {
25+
return [
26+
key,
27+
(...args: unknown[]) => {
28+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
29+
return errors.throwIfNullOrUndefined(value(...args), 'unexpected void return')
30+
},
31+
]
32+
})
33+
34+
return Object.fromEntries(entriesWithWrappedValueCall) as NonVoidRecordMethods<T>
35+
}

packages/common/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from './utils'
2+
export * from './errors'
3+
export * from './types'
4+
export * from './ManualChangeList'
5+
export * from './testing'

0 commit comments

Comments
 (0)