diff --git a/transforms/no-implicit-this/__testfixtures__/-mock-telemetry.json b/transforms/no-implicit-this/__testfixtures__/-mock-telemetry.json index 3042eb37..c0ad18a9 100644 --- a/transforms/no-implicit-this/__testfixtures__/-mock-telemetry.json +++ b/transforms/no-implicit-this/__testfixtures__/-mock-telemetry.json @@ -5,58 +5,6 @@ "block-component": { "type": "Component" }, "foo": { "type": "Component" }, "namespace/foo": { "type": "Component" }, - "built-in-helpers": { - "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" } } diff --git a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.input.hbs b/transforms/no-implicit-this/__testfixtures__/dont-assume-this.input.hbs deleted file mode 100644 index 1673350f..00000000 --- a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.input.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{foo}} -{{bar}} \ No newline at end of file diff --git a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.options.json b/transforms/no-implicit-this/__testfixtures__/dont-assume-this.options.json deleted file mode 100644 index 9000ec68..00000000 --- a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dontAssumeThis": true -} \ No newline at end of file diff --git a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.output.hbs b/transforms/no-implicit-this/__testfixtures__/dont-assume-this.output.hbs deleted file mode 100644 index a61e4799..00000000 --- a/transforms/no-implicit-this/__testfixtures__/dont-assume-this.output.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{this.foo}} -{{bar}} \ No newline at end of file diff --git a/transforms/no-implicit-this/__testfixtures__/handlebars-without-params.output.hbs b/transforms/no-implicit-this/__testfixtures__/handlebars-without-params.output.hbs index df1fe49e..4062ce34 100644 --- a/transforms/no-implicit-this/__testfixtures__/handlebars-without-params.output.hbs +++ b/transforms/no-implicit-this/__testfixtures__/handlebars-without-params.output.hbs @@ -1,6 +1,6 @@ {{my-component}} {{a-helper}} -{{this.foo}} +{{foo}} {{this.property}} {{namespace/foo}} {{this.someGetter}} diff --git a/transforms/no-implicit-this/helpers/determine-this-usage.js b/transforms/no-implicit-this/helpers/determine-this-usage.js deleted file mode 100644 index de2bae1f..00000000 --- a/transforms/no-implicit-this/helpers/determine-this-usage.js +++ /dev/null @@ -1,30 +0,0 @@ -const path = require('path'); -const recast = require('ember-template-recast'); -const { getTelemetryFor } = require('ember-codemods-telemetry-helpers'); - -const logger = require('./log-helper'); -const transformPlugin = require('./plugin'); - -/** - * Main entry point for parsing and inserting 'this' where appropriate - * - * @param {*} ast - */ -function determineThisUsage(ast, file, options) { - let { path: filePath } = file; - let runtimeData = getTelemetryFor(path.resolve(filePath)); - - if (!runtimeData) { - let msg = `[${filePath}]: SKIPPED Could not find runtime data NO_RUNTIME_DATA`; - logger.warn(msg); - return; - } - - recast.transform(ast, env => transformPlugin(env, runtimeData, options)); - - return ast; -} - -module.exports = { - determineThisUsage, -}; diff --git a/transforms/no-implicit-this/helpers/plugin.js b/transforms/no-implicit-this/helpers/plugin.js index 87e49ba2..f5b637ab 100644 --- a/transforms/no-implicit-this/helpers/plugin.js +++ b/transforms/no-implicit-this/helpers/plugin.js @@ -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) { @@ -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 {{bar}} + 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) { + //
+ handleParams(node.params); + + //
+ handleHash(node.hash); + }, + }; } function populateInvokeables() { diff --git a/transforms/no-implicit-this/index.js b/transforms/no-implicit-this/index.js index 2c9940ba..a31a92f0 100644 --- a/transforms/no-implicit-this/index.js +++ b/transforms/no-implicit-this/index.js @@ -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'); 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 @@ -35,7 +33,6 @@ function _getCustomHelpersFromConfig(configPath) { function getOptions() { let cliOptions = getCLIOptions(); let options = { - dontAssumeThis: cliOptions.dontAssumeThis, customHelpers: _getCustomHelpersFromConfig(cliOptions.config), }; return options; @@ -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; };