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

Rewrite PathExpression visitor code #62

Merged
merged 1 commit into from
Dec 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 0 additions & 52 deletions transforms/no-implicit-this/__testfixtures__/-mock-telemetry.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,6 @@
"block-component": { "type": "Component" },
"foo": { "type": "Component" },
"namespace/foo": { "type": "Component" },
"built-in-helpers": {
Turbo87 marked this conversation as resolved.
Show resolved Hide resolved
"type": "Component",
"computedProperties": ["foo", "records"],
"ownActions": ["myAction"]
},
"custom-helpers": { "type": "Component" },
"angle-brackets-with-block-params": {
"type": "Component",
"computedProperties": ["foo", "property", "bar"],
"ownActions": ["myAction"]
},
"angle-brackets-with-hash-params": {
"type": "Component",
"computedProperties": ["foo", "property"],
"ownActions": ["myAction"]
},
"angle-brackets-without-params": {
"type": "Component",
"computedProperties": ["foo"]
},
"dont-assume-this": {
"type": "Component",
"computedProperties": ["foo"]
},
"handlebars-with-positional-params": {
"type": "Component",
"computedProperties": ["foo", "property"],
"ownActions": ["myAction"]
},
"handlebars-with-wall-street-syntax": {
"type": "Component",
"computedProperties": ["foo", "property"],
"ownActions": ["myAction"]
},
"handlebars-with-hash-params": {
"type": "Component",
"computedProperties": ["foo", "property"],
"ownActions": ["myAction"]
},
"handlebars-with-block-params": {
"type": "Component",
"computedProperties": ["foo", "property"]
},
"handlebars-without-params": {
"type": "Component",
"computedProperties": ["foo", "property"],
"getters": ["someGetter"]
},
"void-elements": {
"type": "Component",
"computedProperties": ["previewImageUrl"]
},
"my-helper": { "type": "Helper" },
"a-helper": { "type": "Helper" }
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{{my-component}}
{{a-helper}}
{{this.foo}}
{{foo}}
{{this.property}}
{{namespace/foo}}
{{this.someGetter}}
30 changes: 0 additions & 30 deletions transforms/no-implicit-this/helpers/determine-this-usage.js

This file was deleted.

166 changes: 100 additions & 66 deletions transforms/no-implicit-this/helpers/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ const KNOWN_HELPERS = require('./known-helpers');
/**
* plugin entrypoint
*/
function transformPlugin(env, runtimeData, options = {}) {
function transformPlugin(env, options = {}) {
let { builders: b } = env.syntax;

let scopedParams = [];
let [components, helpers] = populateInvokeables();

let nonThises = { scopedParams, components, helpers };
let customHelpers = options.customHelpers || [];

let paramTracker = {
enter(node) {
Expand All @@ -29,87 +29,121 @@ function transformPlugin(env, runtimeData, options = {}) {
},
};

return {
Program: paramTracker,
ElementNode: paramTracker,
PathExpression(ast) {
if (ast.data) return;
if (ast.original === 'this') return;
function handleParams(params) {
for (let param of params) {
if (param.type !== 'PathExpression') continue;
handlePathExpression(param);
}
}

let token = ast.parts[0];
function handleHash(hash) {
for (let pair of hash.pairs) {
if (pair.value.type !== 'PathExpression') continue;
handlePathExpression(pair.value);
}
}

if (token !== 'this') {
let isThisNeeded = doesTokenNeedThis(token, nonThises, runtimeData, options);
function handlePathExpression(node) {
// skip this.foo
if (node.this) return;

if (isThisNeeded) {
return b.path(`this.${ast.parts.join('.')}`);
}
}
},
};
}
// skip @foo
if (node.data) return;

// skip {#foo as |bar|}}{{bar}}{{/foo}}
// skip <Foo as |bar|>{{bar}}</Foo>
let firstPart = node.parts[0];
if (scopedParams.includes(firstPart)) return;

// Does the runtime data (for the c
// urrent file)
// contain a definition for the token?
// - yes:
// - in-let: false
// - in-each: false
// - true
// - no:
// - is-helper: false
// - is-component: false
function doesTokenNeedThis(
token,
{ components, helpers, scopedParams },
runtimeData,
{ dontAssumeThis, customHelpers }
) {
if (KNOWN_HELPERS.includes(token) || customHelpers.includes(token)) {
return false;
// add `this.` prefix
Object.assign(node, b.path(`this.${node.original}`));
}

let isBlockParam = scopedParams.includes(token);
function isHelper(name) {
return (
KNOWN_HELPERS.includes(name) ||
customHelpers.includes(name) ||
Boolean(helpers.find(path => path.endsWith(name)))
);
}

if (isBlockParam) {
return false;
function isComponent(name) {
return Boolean(components.find(path => path.endsWith(name)));
}

let { computedProperties, getters, ownActions, ownProperties } = runtimeData;
let isComputed = (computedProperties || []).includes(token);
let isAction = (ownActions || []).includes(token);
let isProperty = (ownProperties || []).includes(token);
let isGetter = (getters || []).includes(token);
let inAttrNode = false;

let needsThis = isComputed || isAction || isProperty || isGetter;
return {
Block: paramTracker,
ElementNode: paramTracker,

if (needsThis) {
return true;
}
AttrNode: {
enter() {
inAttrNode = true;
},
exit() {
inAttrNode = false;
},
},

// This is to support the ember-holy-futuristic-template-namespacing-batman syntax
// as well as support for Nested Invocations in Angle Bracket Syntax
// Ref: https://github.com/rwjblue/ember-holy-futuristic-template-namespacing-batman
if (token.includes('$')) {
token = token.split('$')[1];
}
if (token.includes('::')) {
token = token.replace(/::/g, '/');
}
MustacheStatement(node) {
let { path, params, hash } = node;

let isComponent = components.find(path => path.endsWith(token));
// {{foo BAR}}
handleParams(params);

if (isComponent) {
return false;
}
// {{foo bar=BAZ}}
handleHash(hash);

let isHelper = helpers.find(path => path.endsWith(token));
let hasParams = params.length !== 0;
let hasHashPairs = hash.pairs.length !== 0;

if (isHelper) {
return false;
}
// {{FOO}}
if (path.type === 'PathExpression' && !hasParams && !hasHashPairs) {
// {{FOO.bar}}
if (path.parts > 1) {
handlePathExpression(path);
return;
}

// skip ember-holy-futuristic-template-namespacing-batman component/helper invocations
// (see https://github.com/rwjblue/ember-holy-futuristic-template-namespacing-batman)
if (path.original.includes('$') || path.original.includes('::')) return;

// skip helpers
if (isHelper(path.original)) return;

// skip components
if (!inAttrNode && isComponent(path.original)) return;

handlePathExpression(path);
}
},

return dontAssumeThis ? false : true;
BlockStatement(node) {
// {{#foo BAR}}{{/foo}}
handleParams(node.params);

// {{#foo bar=BAZ}}{{/foo}}
handleHash(node.hash);
},

SubExpression(node) {
// (foo BAR)
handleParams(node.params);

// (foo bar=BAZ)
handleHash(node.hash);
},

ElementModifierStatement(node) {
// <div {{foo BAR}} />
handleParams(node.params);

// <div {{foo bar=BAZ}} />
Turbo87 marked this conversation as resolved.
Show resolved Hide resolved
handleHash(node.hash);
},
};
}

function populateInvokeables() {
Expand Down
19 changes: 4 additions & 15 deletions transforms/no-implicit-this/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
const path = require('path');
const fs = require('fs');

const { parse: parseHbs, print: printHbs } = require('ember-template-recast');
const { determineThisUsage } = require('./helpers/determine-this-usage');
const recast = require('ember-template-recast');
const transformPlugin = require('./helpers/plugin');
Turbo87 marked this conversation as resolved.
Show resolved Hide resolved
const { getOptions: getCLIOptions } = require('codemod-cli');
const DEFAULT_OPTIONS = {
dontAssumeThis: false,
};
const DEFAULT_OPTIONS = {};

/**
* Accepts the config path for custom helpers and returns the array of helpers
Expand Down Expand Up @@ -35,7 +33,6 @@ function _getCustomHelpersFromConfig(configPath) {
function getOptions() {
let cliOptions = getCLIOptions();
let options = {
dontAssumeThis: cliOptions.dontAssumeThis,
customHelpers: _getCustomHelpersFromConfig(cliOptions.config),
};
return options;
Expand All @@ -50,13 +47,5 @@ module.exports = function transformer(file /*, api */) {
return;
}

let root = parseHbs(file.source);

let replaced = determineThisUsage(root, file, options);

if (replaced) {
return printHbs(replaced);
}

return file.source;
return recast.transform(file.source, env => transformPlugin(env, options)).code;
};