Skip to content

Commit

Permalink
feat(rule): update internal imports of a moved file
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMason committed Jul 7, 2019
1 parent 3446d84 commit 5dcba17
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 22 deletions.
101 changes: 99 additions & 2 deletions src/move-files.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { readFileSync } from 'fs';
import { existsSync, readFileSync } from 'fs';
import * as mock from 'mock-fs';
import rule from './move-files';
import { ruleTester } from './test/rule-tester';

const readTextFileSync = (filePath: string) =>
readFileSync(filePath, { encoding: 'utf8' });

beforeAll(() => {
console.log('https://github.com/facebook/jest/issues/5792');
});
Expand All @@ -21,8 +24,12 @@ describe('when no files are provided', () => {
});

describe('when renaming one file', () => {
const depPath = `/fake/dir/dep.js`;
const oldPath = `/fake/dir/old-name.js`;
const newPath = `/fake/dir/new-name.js`;
const depContents = `
export const dep = 1;
`;
const fileContents = `
import { dep } from './dep';
export const a = 1;
Expand All @@ -41,7 +48,10 @@ describe('when renaming one file', () => {

describe('when file has the old name', () => {
beforeEach(() => {
mock({ [oldPath]: fileContents });
mock({
[depPath]: depContents,
[oldPath]: fileContents
});
});

afterEach(() => {
Expand Down Expand Up @@ -124,3 +134,90 @@ describe('when renaming one file', () => {
});
});
});

[
{
depPath: `/fake/old-dir/dep.js`,
oldPath: `/fake/old-dir/file.js`,
newPath: `/fake/new-dir/file.js`,
moduleId: './dep',
newModuleId: '../old-dir/dep'
},
{
depPath: `/fake/lib/dep.js`,
oldPath: `/fake/file.js`,
newPath: `/fake/lib/file.js`,
moduleId: './lib/dep',
newModuleId: './dep'
},
{
depPath: `/dep.js`,
oldPath: `/fake/old-dir/name.js`,
newPath: `/fake/new-dir/name.js`,
moduleId: '../../dep',
newModuleId: '../../dep'
},
{
depPath: `/fake/old-dir/dep.js`,
oldPath: `/fake/old-dir/file.js`,
newPath: `/file.js`,
moduleId: './dep',
newModuleId: './fake/old-dir/dep'
}
].forEach(({ depPath, oldPath, newPath, moduleId, newModuleId }) => {
describe(`when moving one file from ${oldPath} to ${newPath}`, () => {
const options = [{ files: { [oldPath]: newPath } }];
const depContents = `export const dep = 1;`;
const fileContents = `import { dep } from '${moduleId}'; export const a = 1;`;
const newFileContents = `import { dep } from '${newModuleId}'; export const a = 1;`;
const errors = [
{ message: `${oldPath} has moved to ${newPath}` },
{ message: `${oldPath} has moved to ${newPath}` }
];

describe('when file has already been moved', () => {
it('is valid', () => {
ruleTester.run('move-files', rule, {
valid: [{ code: '', filename: newPath, options }],
invalid: []
});
});
});

describe('when file is in the old location', () => {
beforeEach(() => {
mock({
[depPath]: depContents,
[oldPath]: fileContents
});
});

afterEach(() => {
mock.restore();
});

it('moves the file and updates its imports', () => {
ruleTester.run('move-files', rule, {
valid: [],
invalid: [
{
code: fileContents,
errors,
filename: oldPath,
options,
output: newFileContents
}
]
});
// ESLint's RuleTester does not write to Disk, but we can assert that:
// 1. The File in its old location had its imports updated (via the
// `output` property above).
// 2. A file was written in the new location containing the *old*
// contents, in reality this would be the new contents with the
// updated imports.
expect(readTextFileSync(newPath)).toEqual(fileContents);
expect(existsSync(newPath)).toEqual(true);
});
});
});
});
66 changes: 46 additions & 20 deletions src/move-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,67 @@ const rule: Rule.RuleModule = {
]
},
create: (context) => {
const source = context.getSourceCode();
const files = getIn('options.0.files', context, {});
const currentFilePath = context.getFilename();
const dirPath = dirname(currentFilePath);
const newFilePath = files[currentFilePath];
const isFileBeingMoved = Boolean(newFilePath);

const withLeadingDot = (moduleId: string) =>
moduleId.startsWith('.') ? moduleId : `./${moduleId}`;
moduleId.startsWith('.') || moduleId.startsWith('/')
? moduleId
: `./${moduleId}`;

const withoutFileExtension = (filePath: string) =>
filePath.replace(/\.[^.]+$/, '');

const getNewModuleId = (newPath: string) =>
withLeadingDot(withoutFileExtension(relative(dirPath, newPath)));
const getNewModuleId = (xx: string) =>
withLeadingDot(withoutFileExtension(xx));

const withFileExtension = (filePath: string) => {
try {
return require.resolve(filePath);
} catch (e) {
return filePath;
return `${filePath}.js`;
}
};

if (isFileBeingMoved) {
return {
'ImportDeclaration[source.value=/^\\./]'(n: EsTree.Node) {
const node = n as any;
const moduleId = node.source.value;
const newDirPath = dirname(newFilePath);
if (dirPath !== newDirPath) {
const quotes = node.source.raw.charAt(0);
const modulePath = withFileExtension(resolve(dirPath, moduleId));
const newPathToModule = relative(newDirPath, modulePath);
const newModuleId = getNewModuleId(newPathToModule);
const withQuotes = `${quotes}${newModuleId}${quotes}`;
return context.report({
fix(fixer) {
return fixer.replaceText(node.source, withQuotes);
},
message: ERROR_MOVED_FILE(currentFilePath, newFilePath),
node: node.source
});
}
},
'Program:exit'(n: EsTree.Node) {
const node = n as EsTree.Program;
context.report({
fix(fixer) {
moveSync(currentFilePath, newFilePath, { overwrite: true });
return fixer.insertTextAfter(node, '');
},
message: ERROR_MOVED_FILE(currentFilePath, newFilePath),
node
});
}
};
}

return {
'CallExpression[callee.name="require"][arguments.0.value=/^\\./]'(
n: EsTree.Node
Expand All @@ -60,7 +100,7 @@ const rule: Rule.RuleModule = {
const modulePath = withFileExtension(resolve(dirPath, moduleId));
const newModulePath = files[modulePath];
if (newModulePath) {
const newModuleId = getNewModuleId(newModulePath);
const newModuleId = getNewModuleId(relative(dirPath, newModulePath));
const withQuotes = `${quotes}${newModuleId}${quotes}`;
context.report({
fix: (fixer) => fixer.replaceText(node.arguments[0], withQuotes),
Expand All @@ -76,28 +116,14 @@ const rule: Rule.RuleModule = {
const modulePath = withFileExtension(resolve(dirPath, moduleId));
const newModulePath = files[modulePath];
if (newModulePath) {
const newModuleId = getNewModuleId(newModulePath);
const newModuleId = getNewModuleId(relative(dirPath, newModulePath));
const withQuotes = `${quotes}${newModuleId}${quotes}`;
context.report({
fix: (fixer) => fixer.replaceText(node.source, withQuotes),
message: ERROR_MOVED_FILE(modulePath, newModulePath),
node
});
}
},
'Program:exit'(n: EsTree.Node) {
const node = n as EsTree.Program;
const newFilePath = files[currentFilePath];
if (newFilePath) {
context.report({
fix(fixer) {
moveSync(currentFilePath, newFilePath, { overwrite: true });
return fixer.insertTextAfter(node, '');
},
message: ERROR_MOVED_FILE(currentFilePath, newFilePath),
node
});
}
}
};
}
Expand Down

0 comments on commit 5dcba17

Please sign in to comment.