Skip to content

Commit 31287f1

Browse files
committed
fix(rule): handle fixer being run multiple times
1 parent d2b8df4 commit 31287f1

File tree

1 file changed

+65
-54
lines changed

1 file changed

+65
-54
lines changed

src/move-files.ts

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
import { Rule } from 'eslint';
22
import * as EsTree from 'estree';
3-
import { outputFileSync, removeSync } from 'fs-extra';
3+
import { copyFileSync, removeSync } from 'fs-extra';
44
import * as glob from 'glob';
55
import { basename, dirname, join, relative, resolve } from 'path';
66
import { ERROR_MOVED_FILE } from './config';
77
import { getIn } from './lib/get-in';
88
import { interpolate } from './lib/path-reader';
99

10-
type FixList = Array<(fixer: Rule.RuleFixer) => Rule.Fix>;
11-
1210
interface FileIndex {
1311
[source: string]: string;
1412
}
1513

16-
interface ModuleStrategy {
17-
getModuleId: (node: any) => string;
18-
getQuotes: (node: any) => string;
19-
getSource: (node: any) => EsTree.Identifier;
14+
interface VisitedFiles {
15+
[source: string]: boolean;
2016
}
2117

18+
const visitedFiles: VisitedFiles = {};
19+
2220
const isDir = (path: string) => !basename(path).includes('.');
2321

2422
const withLeadingDot = (moduleId: string) =>
@@ -29,7 +27,7 @@ const withLeadingDot = (moduleId: string) =>
2927
const withoutFileExtension = (filePath: string) =>
3028
filePath.replace(/\.[^.]+$/, '');
3129

32-
const getNewModuleId = (filePath: string) =>
30+
const getNewDepId = (filePath: string) =>
3331
withLeadingDot(withoutFileExtension(filePath));
3432

3533
const withFileExtension = (filePath: string) => {
@@ -41,27 +39,39 @@ const withFileExtension = (filePath: string) => {
4139
};
4240

4341
const updateMovedFile = (
42+
context: Rule.RuleContext,
4443
files: FileIndex,
45-
dirPath: string,
44+
fileDirPath: string,
4645
newFilePath: string,
4746
n: EsTree.Node,
48-
fixes: FixList,
49-
{ getModuleId, getQuotes, getSource }: ModuleStrategy
47+
getDepId: (node: any) => string
5048
) => {
5149
const node = n as any;
52-
const moduleId = getModuleId(node);
53-
if (!moduleId.startsWith('.')) {
50+
const depId = getDepId(node);
51+
if (!depId.startsWith('.')) {
5452
return;
5553
}
56-
const newDirPath = dirname(newFilePath);
57-
if (newDirPath !== dirPath) {
58-
const quotes = getQuotes(node);
59-
const rawModulePath = withFileExtension(resolve(dirPath, moduleId));
60-
const modulePath = files[rawModulePath] || rawModulePath;
61-
const newPathToModule = relative(newDirPath, modulePath);
62-
const newModuleId = getNewModuleId(newPathToModule);
63-
const withQuotes = `${quotes}${newModuleId}${quotes}`;
64-
fixes.push((fixer) => fixer.replaceText(getSource(node), withQuotes));
54+
const newFileDirPath = dirname(newFilePath);
55+
if (newFileDirPath !== fileDirPath) {
56+
const depPath = withFileExtension(resolve(fileDirPath, depId));
57+
const newDepPath = files[depPath] || depPath;
58+
const newPathToDep = relative(newFileDirPath, newDepPath);
59+
const newDepId = getNewDepId(newPathToDep);
60+
return context.report({
61+
fix: (fixer) =>
62+
fixer.replaceText(
63+
node,
64+
context
65+
.getSourceCode()
66+
.getText(node)
67+
.replace(depId, newDepId)
68+
),
69+
message: ERROR_MOVED_FILE(
70+
withLeadingDot(relative(process.cwd(), depPath)),
71+
withLeadingDot(relative(process.cwd(), newDepPath))
72+
),
73+
node
74+
});
6575
}
6676
};
6777

@@ -70,41 +80,37 @@ const updateConsumer = (
7080
files: FileIndex,
7181
dirPath: string,
7282
n: EsTree.Node,
73-
{ getModuleId, getQuotes, getSource }: ModuleStrategy
83+
getDepId: (node: any) => string
7484
) => {
7585
const node = n as any;
76-
const moduleId = getModuleId(node);
86+
const moduleId = getDepId(node);
7787
if (!moduleId.startsWith('.')) {
7888
return;
7989
}
80-
const quotes = getQuotes(node);
8190
const modulePath = withFileExtension(resolve(dirPath, moduleId));
8291
const newModulePath = files[modulePath];
8392
if (newModulePath) {
84-
const newModuleId = getNewModuleId(relative(dirPath, newModulePath));
85-
const withQuotes = `${quotes}${newModuleId}${quotes}`;
93+
const newModuleId = getNewDepId(relative(dirPath, newModulePath));
8694
return context.report({
87-
fix: (fixer) => fixer.replaceText(getSource(node), withQuotes),
95+
fix: (fixer) =>
96+
fixer.replaceText(
97+
node,
98+
context
99+
.getSourceCode()
100+
.getText(node)
101+
.replace(moduleId, newModuleId)
102+
),
88103
message: ERROR_MOVED_FILE(
89104
withLeadingDot(relative(process.cwd(), modulePath)),
90105
withLeadingDot(relative(process.cwd(), newModulePath))
91106
),
92-
node: getSource(node)
107+
node
93108
});
94109
}
95110
};
96111

97-
const requireStrategy: ModuleStrategy = {
98-
getModuleId: (node: any) => getIn('arguments.0.value', node, ''),
99-
getQuotes: (node: any) => node.arguments[0].raw.charAt(0),
100-
getSource: (node: any) => node.arguments[0]
101-
};
102-
103-
const importStrategy: ModuleStrategy = {
104-
getModuleId: (node: any) => node.source.value,
105-
getQuotes: (node: any) => node.source.raw.charAt(0),
106-
getSource: (node: any) => node.source
107-
};
112+
const getRequireDepId = (node: any) => getIn('arguments.0.value', node, '');
113+
const getImportDepId = (node: any) => node.source.value;
108114

109115
const rule: Rule.RuleModule = {
110116
meta: {
@@ -130,7 +136,14 @@ const rule: Rule.RuleModule = {
130136
]
131137
},
132138
create: (context) => {
133-
const sourceCode = context.getSourceCode();
139+
const currentFilePath = context.getFilename();
140+
141+
if (visitedFiles[currentFilePath]) {
142+
return {};
143+
}
144+
145+
visitedFiles[currentFilePath] = true;
146+
134147
const patterns: FileIndex = getIn('options.0.files', context, {});
135148
const files: FileIndex = {};
136149

@@ -147,43 +160,41 @@ const rule: Rule.RuleModule = {
147160
});
148161
});
149162

150-
const currentFilePath = context.getFilename();
151163
const dirPath = dirname(currentFilePath);
152164
const newFilePath = files[currentFilePath];
153165
const isFileBeingMoved = Boolean(newFilePath);
154166

155167
if (isFileBeingMoved) {
156-
const fixes: FixList = [];
157168
return {
158169
'CallExpression[callee.name="require"]'(n: EsTree.Node) {
159170
return updateMovedFile(
171+
context,
160172
files,
161173
dirPath,
162174
newFilePath,
163175
n,
164-
fixes,
165-
requireStrategy
176+
getRequireDepId
166177
);
167178
},
168179
ImportDeclaration(n: EsTree.Node) {
169180
return updateMovedFile(
181+
context,
170182
files,
171183
dirPath,
172184
newFilePath,
173185
n,
174-
fixes,
175-
importStrategy
186+
getImportDepId
176187
);
177188
},
178189
'Program:exit'(n: EsTree.Node) {
179190
const node = n as EsTree.Program;
180191
return context.report({
181192
fix(fixer) {
182-
const contents = sourceCode.getText();
183-
process.nextTick(() => removeSync(currentFilePath));
184-
outputFileSync(newFilePath, contents);
185-
// ESLint's types don't reflect that an array of fixes can be returned
186-
return (fixes.map((fn) => fn(fixer)) as unknown) as Rule.Fix;
193+
process.nextTick(() => {
194+
copyFileSync(currentFilePath, newFilePath);
195+
removeSync(currentFilePath);
196+
});
197+
return fixer.insertTextAfter(node, '');
187198
},
188199
message: ERROR_MOVED_FILE(
189200
withLeadingDot(relative(process.cwd(), currentFilePath)),
@@ -197,10 +208,10 @@ const rule: Rule.RuleModule = {
197208

198209
return {
199210
'CallExpression[callee.name="require"]'(n: EsTree.Node) {
200-
return updateConsumer(context, files, dirPath, n, requireStrategy);
211+
return updateConsumer(context, files, dirPath, n, getRequireDepId);
201212
},
202213
ImportDeclaration(n: EsTree.Node) {
203-
return updateConsumer(context, files, dirPath, n, importStrategy);
214+
return updateConsumer(context, files, dirPath, n, getImportDepId);
204215
}
205216
};
206217
}

0 commit comments

Comments
 (0)