Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update literal paths #21

Merged
merged 15 commits into from
Jul 29, 2019
5 changes: 5 additions & 0 deletions __testfixtures__/literal.input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Model, { attr } from 'ember-data/model';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, it seems we have a wrong import. It should be:

 import Model from 'ember-data/model';
 import attr from 'ember-data/attr';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't make the test fail, because attr is identified in the modules lookup.

import { hasMany, belongsTo } from 'ember-data/relationships';
import JSONAPIAdapter from 'ember-data/adapters/json-api';
import { InvalidError, ServerError, TimeoutError, NotFoundError } from 'ember-data/adapter/error';
import Transform from '@ember-data/serializer/transform';
4 changes: 4 additions & 0 deletions __testfixtures__/literal.output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';
import JSONAPIAdapter from '@ember-data/adapter/json-api';
import { InvalidError, ServerError, TimeoutError, NotFoundError } from '@ember-data/adapter/error';
import Transform from '@ember-data/serializer/transform';
6 changes: 4 additions & 2 deletions bin/ember-data-codemod.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function buildReport() {

// Find all of the temporary logs from the worker processes, which contain a
// serialized JSON array on each line.
glob('ember-modules-codemod.tmp.*', (err, logs) => {
glob('ember-data-codemod.tmp.*', (err, logs) => {
// If no worker found an unexpected value, nothing to report.
if (!logs) {
return;
Expand Down Expand Up @@ -143,7 +143,9 @@ function buildReport() {
);
} else {
console.log(
chalk.green('\nDone! All uses of the Ember global have been updated.')
chalk.green(
'\nDone! All uses of the Ember global and Ember Data imports have been updated.'
)
);
}
});
Expand Down
83 changes: 80 additions & 3 deletions transforms/globals-to-ember-data-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function transform(file, api /*, options*/) {
// Now that we've identified all of the replacements that we need to do, we'll
// make sure to either add new `import` declarations, or update existing ones
// to add new named exports or the default export.
updateOrCreateImportDeclarations(root, modules);
updateOrCreateImportDeclarations(root, modules, mappings);

// Actually go through and replace each usage of `DS.whatever` with the
// imported binding (`whatever`).
Expand Down Expand Up @@ -157,6 +157,76 @@ function transform(file, api /*, options*/) {
return dsUsages.filter(isDSGlobal(globalDS)).paths();
}

/*
* loops through all modules and replaces literal path if necessary
* 'ember-data/model' -> '@ember-data/model'
*/
function updateExistingLiteralPaths(root, module, mappings) {
let foundMapping = mappings[module.local];

if (foundMapping) {
let newSource = foundMapping.source;
if (module.source !== newSource) {
root
.find(j.ImportDeclaration, {
source: {
type: 'Literal',
value: module.source
}
})
.find(j.Literal)
.forEach(importLiteral => {
j(importLiteral).replaceWith(j.literal(newSource));
});
}
}
}

/*
* After modifying existing sources to their new paths, we need
* to make sure we clean up duplicate imports
*/
function cleanupDuplicateLiteralPaths() {
Copy link
Contributor Author

@snewcomer snewcomer Jul 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to use the existing registry.modules somehow, but at this point in the codemod, they might contain old information. So in this solution, I went with iterating over the imports (post updating their source) and adding to the existing import if found.

const uniqueImports = {};

root.find(j.ImportDeclaration).forEach(nodePath => {
let node = nodePath.node;
let value = node.source && node.source.value;

if (!(value in uniqueImports)) {
// add to found uniqueImports and we wont modify
uniqueImports[value] = nodePath;
} else {
// get all specifiers and add to existing import
// then delete this nodePath
let specifiers = node.specifiers;
let existingNodePath = uniqueImports[value];

specifiers.forEach(spec => {
let local = spec.local;
let imported = spec.imported;

if (imported === 'default') {
let specifier = j.importDefaultSpecifier(j.identifier(local));
// default imports go at front
existingNodePath.get('specifiers').unshift(specifier);
} else if (imported && local) {
let specifier = j.importSpecifier(
j.identifier(imported.name),
j.identifier(local.name)
);
existingNodePath.get('specifiers').push(specifier);
} else {
let specifier = j.importSpecifier(j.identifier(local.name));
existingNodePath.get('specifiers').push(specifier);
}
});

nodePath.prune();
}
});
}

// Find destructured global aliases for fields on the DS global
function findGlobalDSAliases(root, globalDS, mappings) {
let aliases = {};
Expand Down Expand Up @@ -436,7 +506,7 @@ function transform(file, api /*, options*/) {
return parent.node.id.name === local;
}

function updateOrCreateImportDeclarations(root, registry) {
function updateOrCreateImportDeclarations(root, registry, mappings) {
let body = root.get().value.program.body;

registry.modules.forEach(mod => {
Expand All @@ -448,12 +518,12 @@ function transform(file, api /*, options*/) {
let declaration = root.find(j.ImportDeclaration, {
source: { value: mod.source }
});

if (declaration.size() > 0) {
let specifier;

if (imported === 'default') {
specifier = j.importDefaultSpecifier(j.identifier(local));
// default imports go at front
declaration.get('specifiers').unshift(specifier);
} else {
specifier = j.importSpecifier(
Expand All @@ -472,7 +542,14 @@ function transform(file, api /*, options*/) {
mod.node = importStatement;
}
}

// Update literal paths based on mappings from 'ember-data/model' to '@ember-data/model'
// by pushing into existing declaration specifiers
updateExistingLiteralPaths(root, mod, mappings);
});

// then remove old duplicate specifier if found
cleanupDuplicateLiteralPaths();
}

function findExistingModules(root) {
Expand Down