Skip to content

Commit

Permalink
Handle shorthand actions
Browse files Browse the repository at this point in the history
  • Loading branch information
Santosh Sutar committed Nov 22, 2018
1 parent 6f0b1af commit 12d715a
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 48 deletions.
1 change: 1 addition & 0 deletions transforms/ember-object/__testfixtures__/basic.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Foo = Test.extend(MyMixin, {
numProp: 123,
[MY_VAL]: "val",
queryParams: {},
someVal,

/**
* Method comments
Expand Down
1 change: 1 addition & 0 deletions transforms/ember-object/__testfixtures__/basic.output.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class Foo extends Test.extend(MyMixin) {
numProp = 123;
[MY_VAL] = "val";
queryParams = {};
someVal = someVal;

/**
* Method comments
Expand Down
2 changes: 2 additions & 0 deletions transforms/ember-object/__testfixtures__/decorators.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { inject as controller } from "@ember/controller";
import { inject as service } from "@ember/service";
import { on } from "@ember/object/evented";
import layout from "components/templates/foo";
import { someActionUtil } from "some/action/util";

const Foo = EmberObject.extend({
tagName: "div",
Expand All @@ -25,6 +26,7 @@ const Foo = EmberObject.extend({
}),

actions: {
someActionUtil,
/**
Comments
*/
Expand Down
6 changes: 6 additions & 0 deletions transforms/ember-object/__testfixtures__/decorators.output.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { controller } from "@ember-decorators/controller";
import { service } from "@ember-decorators/service";
import { on } from "@ember-decorators/object/evented";
import templateLayout from "components/templates/foo";
import { someActionUtil } from "some/action/util";

@tagName("div")
@classNames("test-class", "custom-class")
Expand All @@ -28,6 +29,11 @@ class Foo extends EmberObject {
return "abc";
}

@action
someActionUtil() {
return someActionUtil.call(this, ...arguments);
}

/**
Comments
*/
Expand Down
4 changes: 3 additions & 1 deletion transforms/ember-object/__testfixtures__/runtime.input.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import RuntimeInput from "common/runtime/input";

/**
* Program comments
*/
const Foo = Test.extend(MyMixin, {
export default RuntimeInput.extend(MyMixin, {
/**
* Property comments
*/
Expand Down
3 changes: 2 additions & 1 deletion transforms/ember-object/__testfixtures__/runtime.output.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { action, off, unobserves } from "@ember-decorators/object";
import RuntimeInput from "common/runtime/input";

/**
* Program comments
*/
class Foo extends Test.extend(MyMixin) {
export default class RuntimeInputEmberObject extends RuntimeInput.extend(MyMixin) {
/**
* Property comments
*/
Expand Down
9 changes: 0 additions & 9 deletions transforms/ember-object/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
const { getOptions } = require("codemod-cli");
const { replaceEmberObjectExpressions } = require("../helpers/parse-helper");
const { getRuntimeData } = require("../helpers/util");

module.exports = function transformer(file, api, opts) {
const j = api.jscodeshift;
const options = Object.assign({}, opts, getOptions());
let { source, path } = file;
const runtimeConfigPath = options["runtime-config-path"];

if (runtimeConfigPath) {
options.runtimeData = getRuntimeData(runtimeConfigPath, path);
if (!options.runtimeData) {
return;
}
}

const root = j(source);

Expand Down
85 changes: 62 additions & 23 deletions transforms/helpers/parse-helper.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
const path = require("path");
const camelCase = require("camelcase");
const {
get,
getOptions,
capitalizeFirstLetter,
startsWithUpperCaseLetter,
DECORATOR_PATHS,
EMBER_DECORATOR_SPECIFIERS,
get,
getOptions,
getRuntimeData,
LAYOUT_IMPORT_SPECIFIER,
METHOD_DECORATORS,
META_DECORATORS,
EMBER_DECORATOR_SPECIFIERS
METHOD_DECORATORS,
startsWithUpperCaseLetter
} = require("./util");
const { hasValidProps, isFileOfType } = require("./validation-helper");
const {
withComments,
hasValidProps,
isFileOfType,
isTestFile
} = require("./validation-helper");
const {
createClass,
createEmberDecoratorSpecifiers,
createImportDeclaration,
createEmberDecoratorSpecifiers
withComments
} = require("./transform-helper");
const EOProp = require("./EOProp");
const logger = require("./log-helper");
Expand Down Expand Up @@ -131,11 +136,11 @@ function getDecoratorInfo(specifier, importPropDecoratorMap) {
const isMethodDecorator = METHOD_DECORATORS.includes(importedName);
return {
decoratorName,
localName,
importedName,
isImportedAs,
isMetaDecorator,
isMethodDecorator
isMethodDecorator,
localName
};
}

Expand Down Expand Up @@ -223,15 +228,15 @@ function getDecoratorImports(j, root) {
function getDecoratorsToImport(instanceProps, decoratorsMap = {}) {
return instanceProps.reduce((specs, prop) => {
return {
attribute: specs.attribute || prop.hasAttributeDecorator,
readOnly: specs.readOnly || prop.hasReadOnly,
action: specs.action || prop.isAction,
layout: specs.layout || prop.isLayout,
tagName: specs.tagName || prop.isTagName,
attribute: specs.attribute || prop.hasAttributeDecorator,
className: specs.className || prop.hasClassNameDecorator,
classNames: specs.classNames || prop.isClassNames,
unobserves: specs.unobserves || prop.hasUnobservesDecorator,
layout: specs.layout || prop.isLayout,
off: specs.off || prop.hasOffDecorator,
readOnly: specs.readOnly || prop.hasReadOnly,
tagName: specs.tagName || prop.isTagName,
unobserves: specs.unobserves || prop.hasUnobservesDecorator,
volatile: specs.volatile || prop.hasVolatile
};
}, decoratorsMap);
Expand Down Expand Up @@ -460,11 +465,21 @@ function getExpressionToReplace(j, eoCallExpression) {
* @param {String} filePath
* @return {String}
*/
function getClassName(j, eoCallExpression, filePath) {
function getClassName(
j,
eoCallExpression,
filePath,
superClassName,
type = "EmberObject"
) {
const varDeclaration = getClosetVariableDeclaration(j, eoCallExpression);
const className =
getVariableName(varDeclaration) || camelCase(path.basename(filePath, "js"));
return capitalizeFirstLetter(className);
let capitalizedClassName = capitalizeFirstLetter(className);
if (capitalizedClassName === superClassName) {
capitalizedClassName += capitalizeFirstLetter(type);
}
return capitalizedClassName;
}

/**
Expand Down Expand Up @@ -499,6 +514,24 @@ function parseEmberObjectCallExpression(eoCallExpression) {
*/
function replaceEmberObjectExpressions(j, root, filePath, options = {}) {
logger.info(`[${filePath}]: BEGIN`);

const runtimeConfigPath = options["runtime-config-path"];

if (runtimeConfigPath) {
options.runtimeData = getRuntimeData(runtimeConfigPath, filePath);
if (!options.runtimeData) {
logger.warn(
`${filePath} SKIPPED Cound not find runtime data NO_RUNTIME_DATA`
);
return;
}
}

if (isTestFile(filePath)) {
logger.warn(`[${filePath}]: Skipping test file`);
return;
}

if (options.type && !isFileOfType(filePath, options.type)) {
logger.warn(
`[${filePath}]: FAILURE Type mismatch, expected type '${
Expand Down Expand Up @@ -536,7 +569,13 @@ function replaceEmberObjectExpressions(j, root, filePath, options = {}) {
const superClassName = get(eoCallExpression, "value.callee.object.name");
const es6ClassDeclaration = createClass(
j,
getClassName(j, eoCallExpression, filePath),
getClassName(
j,
eoCallExpression,
filePath,
superClassName,
get(options, "runtimeData.type")
),
eoProps,
superClassName,
mixins
Expand Down Expand Up @@ -568,11 +607,11 @@ function replaceEmberObjectExpressions(j, root, filePath, options = {}) {
}

module.exports = {
getVariableName,
getEmberObjectProps,
getEmberObjectCallExpressions,
getClassName,
getClosetVariableDeclaration,
getEmberObjectCallExpressions,
getEmberObjectProps,
getExpressionToReplace,
replaceEmberObjectExpressions,
getClassName
getVariableName,
replaceEmberObjectExpressions
};
87 changes: 76 additions & 11 deletions transforms/helpers/transform-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ function withComments(to, from) {
return to;
}

/**
* Creates line comments from passed lines
*
* @param {Object} j - jscodeshift lib reference
* @param {String[]} lines line comments
* @returns {CommentLine[]}
*/
function createLineComments(j, lines = []) {
return lines.map(line => j.commentLine(line));
}
Expand Down Expand Up @@ -65,7 +72,8 @@ function createSuperExpressionStatement(j) {
* Replace instances of `this._super(...arguments)` to `super.methodName(...arguments)`
*
* @param {Object} j - jscodeshift lib reference
* @param {MethodDefinition} methodDefinition - MethodDefinition to replce instances from
* @param {MethodDefinition} methodDefinition - MethodDefinition to replace instances from
* @param {FunctionExpression|EOProp} functionProp - Function expression to get the runtime data
* @returns {MethodDefinition}
*/
function replaceSuperExpressions(j, methodDefinition, functionProp) {
Expand Down Expand Up @@ -129,7 +137,7 @@ function replaceSuperExpressions(j, methodDefinition, functionProp) {
* @param {Object} j - jscodeshift lib reference
* @param {EOProp} functionProp
* @param {Decorator[]} decorators
* @returns {MethodDefinition[]}
* @returns {MethodDefinition}
*/
function createMethodProp(j, functionProp, decorators = []) {
const propKind = functionProp.kind === "init" ? "method" : functionProp.kind;
Expand Down Expand Up @@ -180,10 +188,13 @@ function createConstructor(j, instanceProps = []) {
*
* @param {Object} j - jscodeshift lib reference
* @param {EOProp} instanceProp
* @param {Decorator[]} decorators
* @returns {ClassProperty}
*/
function createClassProp(j, instanceProp) {
const decorators = createInstancePropDecorators(j, instanceProp);
function createClassProp(j, instanceProp, decorators = []) {
if (!decorators.length) {
decorators = createInstancePropDecorators(j, instanceProp);
}

const classProp = withDecorators(
withComments(
Expand All @@ -201,20 +212,70 @@ function createClassProp(j, instanceProp) {
}

/**
* Create action decorators
* Actions with identifier converted to method definition
*
* For example in case of following action
* ```
* import someActionUtil from 'some/action/util';
*
* const Foo = Component.extend({
* actions: {
* someActionUtil
* }
* });
* ```
*
* will be transformed to:
*
* ```
* import someActionUtil from 'some/action/util';
*
* const Foo = Component.extend({
* @action
* someActionUtil() {
* return someActionUtil.call(this, ...arguments);
* }
* });
* ```
* @param {Object} j - jscodeshift lib reference
* @param {EOProp} idAction
* @param {Decorator[]} decorators
* @returns {MethodDefinition}
*/
function convertIdentifierActionToMethod(j, idAction, decorators = []) {
const returnBlock = j.blockStatement([
j.returnStatement(
j.callExpression(
j.memberExpression(idAction.value, j.identifier("call")),
[j.thisExpression(), j.spreadElement(j.identifier("arguments"))]
)
)
]);
const expr = j.functionExpression(null, [], returnBlock);

return withDecorators(
withComments(j.methodDefinition("method", idAction.key, expr), idAction),
decorators
);
}

/**
* Create action decorators
* ```
* Converts
* {
* actions: {
* foo() {}
* }
* }
* ```
* to
* ```
* {
* @action
* foo(){ }
* }
*
* ```
* @param {Object} j - jscodeshift lib reference
* @param {EOProp} actionsProp
* @returns {MethodDefinition[]}
Expand All @@ -224,10 +285,14 @@ function createActionDecoratedProps(j, actionsProp) {
const overriddenActions = get(actionsProp, "overriddenActions") || [];
const actionDecorators = createIdentifierDecorators(j);
return actionProps.map(actionProp => {
actionProp.isAction = true;
actionProp.hasRuntimeData = actionsProp.hasRuntimeData;
actionProp.isOverridden = overriddenActions.includes(actionProp.key.name);
return createMethodProp(j, actionProp, actionDecorators);
if (get(actionProp, "value.type") === "Identifier") {
return convertIdentifierActionToMethod(j, actionProp, actionDecorators);
} else {
actionProp.isAction = true;
actionProp.hasRuntimeData = actionsProp.hasRuntimeData;
actionProp.isOverridden = overriddenActions.includes(actionProp.key.name);
return createMethodProp(j, actionProp, actionDecorators);
}
});
}

Expand Down Expand Up @@ -357,7 +422,7 @@ function createImportDeclaration(j, specifiers, path) {
* @param {Object} j - jscodeshift lib reference
* @param {String[]} pathSpecifiers
* @param {String[]} decoratorsToImport
* @returns {ImportSpecifier}
* @returns {ImportSpecifier[]}
*/
function createEmberDecoratorSpecifiers(
j,
Expand Down
Loading

0 comments on commit 12d715a

Please sign in to comment.