-
Notifications
You must be signed in to change notification settings - Fork 219
/
rename.ts
145 lines (122 loc) · 4.35 KB
/
rename.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/* eslint-disable no-use-before-define, @typescript-eslint/no-use-before-define */
import fs from 'fs';
import path from 'path';
import log from 'updatable-log';
import ts from 'typescript';
import json5 from 'json5';
import json5Writer from 'json5-writer';
interface RenameParams {
rootDir: string;
sources?: string | string[];
}
export default function rename({ rootDir, sources }: RenameParams): number {
const configFile = path.resolve(rootDir, 'tsconfig.json');
if (!fs.existsSync(configFile)) {
log.error('Could not find tsconfig.json at', configFile);
return -1;
}
let jsFiles: string[];
try {
jsFiles = findJSFiles(rootDir, configFile, sources);
} catch (err) {
log.error(err);
return -1;
}
if (jsFiles.length === 0) {
log.info('No JS/JSX files to rename.');
return 0;
}
const toRename = jsFiles
.map((oldFile) => {
let newFile: string | undefined;
if (oldFile.endsWith('.jsx')) {
newFile = oldFile.replace(/\.jsx$/, '.tsx');
} else if (oldFile.endsWith('.js') && jsFileContainsJsx(oldFile)) {
newFile = oldFile.replace(/\.js$/, '.tsx');
} else if (oldFile.endsWith('.js')) {
newFile = oldFile.replace(/\.js$/, '.ts');
}
return { oldFile, newFile };
})
.filter((result): result is { oldFile: string; newFile: string } => !!result.newFile);
log.info(`Renaming ${toRename.length} JS/JSX files in ${rootDir}...`);
toRename.forEach(({ oldFile, newFile }) => {
fs.renameSync(oldFile, newFile);
});
updateProjectJson(rootDir);
log.info('Done.');
return 0;
}
function findJSFiles(rootDir: string, configFile: string, sources?: string | string[]) {
const configFileContents = ts.sys.readFile(configFile);
if (configFileContents == null) {
throw new Error(`Failed to read TypeScript config file: ${configFile}`);
}
const { config, error } = ts.parseConfigFileTextToJson(configFile, configFileContents);
if (error) {
const errorMessage = ts.flattenDiagnosticMessageText(error.messageText, ts.sys.newLine);
throw new Error(
`Error parsing TypeScript config file text to json: ${configFile}\n${errorMessage}`,
);
}
let { include } = config;
// Sources come from either `config.files` or `config.includes`.
// If the --sources flag is set, let's ignore both of those config properties
// and set our own `config.includes` instead.
if (sources !== undefined) {
include = Array.isArray(sources) ? sources : [sources];
delete config.files;
}
const { fileNames, errors } = ts.parseJsonConfigFileContent(
{
...config,
compilerOptions: {
...config.compilerOptions,
// Force JS/JSX files to be included
allowJs: true,
},
include,
},
ts.sys,
rootDir,
);
if (errors.length > 0) {
const errorMessage = ts.formatDiagnostics(errors, {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: () => rootDir,
getNewLine: () => ts.sys.newLine,
});
throw new Error(
`Errors parsing TypeScript config file content: ${configFile}\n${errorMessage}`,
);
}
return fileNames.filter((fileName) => /\.jsx?$/.test(fileName));
}
/**
* Heuristic to determine whether a .js file contains JSX.
*/
function jsFileContainsJsx(jsFileName: string): boolean {
const contents = fs.readFileSync(jsFileName, 'utf8');
return /(from ['"]react['"]|@jsx)/.test(contents) && /<[A-Za-z>]/.test(contents);
}
function updateProjectJson(rootDir: string) {
const projectJsonFile = path.resolve(rootDir, 'project.json');
if (!fs.existsSync(projectJsonFile)) {
return;
}
const projectJsonText = fs.readFileSync(projectJsonFile, 'utf-8');
const projectJson = json5.parse(projectJsonText);
if (projectJson && projectJson.allowedImports) {
projectJson.allowedImports = projectJson.allowedImports.map((allowedImport: string) =>
/.jsx?$/.test(allowedImport) ? allowedImport.replace(/\.js(x?)$/, '.ts$1') : allowedImport,
);
}
if (projectJson && projectJson.layout) {
const { layout } = projectJson;
projectJson.layout = /.jsx?$/.test(layout) ? layout.replace(/\.js(x?)$/, '.ts$1') : layout;
}
const writer = json5Writer.load(projectJsonText);
writer.write(projectJson);
fs.writeFileSync(projectJsonFile, writer.toSource({ quote: 'double' }), 'utf-8');
log.info(`Updated allowedImports in ${projectJsonFile}`);
}