From aef0c077f57c8a91bb41ac3a9871ebcec72cfa04 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 2 Jun 2021 15:36:26 -0400 Subject: [PATCH 1/4] Extract separate global deprecation helpers into standalone module. No logical changes here, just moving the code to detect and generate the message out into a stand alone module (makes it easier to reason about and possible to unit test). Co-authored-by: Stefan Penner --- lib/global-deprecation-utils.js | 307 +++++++++++++++++++++++++++++++ lib/index.js | 308 +------------------------------- 2 files changed, 316 insertions(+), 299 deletions(-) create mode 100644 lib/global-deprecation-utils.js diff --git a/lib/global-deprecation-utils.js b/lib/global-deprecation-utils.js new file mode 100644 index 00000000000..6cf7980c08c --- /dev/null +++ b/lib/global-deprecation-utils.js @@ -0,0 +1,307 @@ +const semver = require('semver'); + +function* walkAddonTree(project, pathToAddon = []) { + for (let addon of project.addons) { + yield [addon, pathToAddon]; + yield* walkAddonTree(addon, [...pathToAddon, `${addon.name}@${addon.pkg.version}`]); + } +} + +function requirementFor(pkg, deps = {}) { + return deps[pkg]; +} + +module.exports = function (project) { + if (process.env.EMBER_ENV === 'production') { + return; + } + + let isYarnProject = ((root) => { + try { + // eslint-disable-next-line node/no-unpublished-require + return require('ember-cli/lib/utilities/is-yarn-project')(root); + } catch { + return undefined; + } + })(project.root); + + let groupedByTopLevelAddon = Object.create(null); + let groupedByVersion = Object.create(null); + let projectInfo; + + for (let [addon, pathToAddon] of walkAddonTree(project)) { + let version = addon.pkg.version; + + if (addon.name === 'ember-cli-babel' && semver.lt(version, '7.26.6')) { + let info; + + if (addon.parent === project) { + let requirement = requirementFor('ember-cli-babel', project.pkg.devDependencies); + let compatible = semver.satisfies('7.26.6', requirement); + + info = projectInfo = { + parent: `${project.name()} (your app)`, + version, + requirement, + compatible, + dormant: false, + path: pathToAddon, + }; + } else { + let requirement = requirementFor('ember-cli-babel', addon.parent.pkg.dependencies); + let compatible = semver.satisfies('7.26.6', requirement); + let dormant = addon.parent._fileSystemInfo + ? addon.parent._fileSystemInfo().hasJSFiles === false + : false; + + let topLevelAddon = addon.parent; + + while (topLevelAddon.parent !== project) { + topLevelAddon = topLevelAddon.parent; + } + + info = { + parent: `${addon.parent.name}@${addon.pkg.version}`, + version, + requirement, + compatible, + dormant, + path: pathToAddon, + }; + + let addons = groupedByTopLevelAddon[topLevelAddon.name] || []; + groupedByTopLevelAddon[topLevelAddon.name] = [...addons, info]; + } + + let group = groupedByVersion[version] || Object.create(null); + groupedByVersion[version] = group; + + let addons = group[info.parent] || []; + group[info.parent] = [...addons, info]; + } + } + + if (Object.keys(groupedByVersion).length === 0) { + return; + } + + let dormantTopLevelAddons = []; + let compatibleTopLevelAddons = []; + let incompatibleTopLevelAddons = []; + + for (let addon of Object.keys(groupedByTopLevelAddon)) { + let group = groupedByTopLevelAddon[addon]; + + if (group.every((info) => info.dormant)) { + dormantTopLevelAddons.push(addon); + } else if (group.every((info) => info.compatible)) { + compatibleTopLevelAddons.push(addon); + } else { + incompatibleTopLevelAddons.push(addon); + } + } + + let suggestions = 'The following steps may help:\n\n'; + + let hasActionableSuggestions = false; + + if (projectInfo) { + suggestions += '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; + hasActionableSuggestions = true; + } else if (compatibleTopLevelAddons.length > 0) { + // Only show the compatible addons if the project itself is up-to-date, because updating the + // project's own dependency on ember-cli-babel to latest may also get these addons to use it + // as well. Otherwise, there is an unnecessary copy in the tree and it needs to be deduped. + if (isYarnProject === true) { + suggestions += + '* Run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; + } else if (isYarnProject === false) { + suggestions += '* Run `npm dedupe`.\n'; + } else { + suggestions += + '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n' + + '* If using npm, run `npm dedupe`.\n'; + } + + hasActionableSuggestions = true; + } + + if (incompatibleTopLevelAddons.length > 0) { + suggestions += '* Upgrade the following addons to the latest version:\n'; + + for (let addon of incompatibleTopLevelAddons) { + suggestions += ` * ${addon}\n`; + } + + hasActionableSuggestions = true; + } + + if (!hasActionableSuggestions) { + // Only show the dormant addons if there are nothing else to do because they are unlikely to + // be the problem. + suggestions += '* Upgrade the following addons to the latest version, if available:\n'; + + for (let addon of dormantTopLevelAddons) { + suggestions += ` * ${addon}\n`; + } + } + + let details = + '\n### Details ###\n\n' + + 'Prior to v7.26.6, ember-cli-babel sometimes transpiled imports into the equivalent Ember Global API, ' + + 'potentially triggering this deprecation message indirectly, ' + + 'even when you did not observe these deprecated usages in your code.\n\n' + + 'The following outdated versions are found in your project:\n'; + + let hasDormantAddons = false; + let hasCompatibleAddons = false; + + for (let version of Object.keys(groupedByVersion).sort(semver.compare)) { + details += `\n* ember-cli-babel@${version}, currently used by:\n`; + + for (let parent of Object.keys(groupedByVersion[version]).sort()) { + let info = groupedByVersion[version][parent][0]; + + details += ` * ${parent}`; + + if (info.dormant) { + details += ' (Dormant)\n'; + hasDormantAddons = true; + } else if (info.compatible) { + details += ' (Compatible)\n'; + hasCompatibleAddons = true; + } else { + details += '\n'; + } + + details += ` * Depends on ember-cli-babel@${groupedByVersion[version][parent][0].requirement}\n`; + + for (let info of groupedByVersion[version][parent]) { + let adddedBy = info.path.slice(0, -1); + + if (adddedBy.length) { + details += ` * Added by ${adddedBy.join(' > ')}\n`; + } + + if (info.compatible) { + hasCompatibleAddons = true; + } + } + } + } + + if (hasDormantAddons) { + details += + '\nNote: Addons marked as "Dormant" does not appear to have any JavaScript files. ' + + 'Therefore, even if they are using an old version ember-cli-babel, they are ' + + 'unlikely to be the cuplrit of this deprecation and can likely be ignored.\n'; + } + + if (hasCompatibleAddons) { + details += `\nNote: Addons marked as "Compatible" are already compatible with ember-cli-babel@7.26.6. `; + + if (projectInfo) { + details += 'Try upgrading your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; + } else { + if (isYarnProject === true) { + details += + 'Try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; + } else if (isYarnProject === false) { + details += 'Try running `npm dedupe`.\n'; + } else { + details += + 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + + 'If using npm, try running `npm dedupe`.\n'; + } + } + } + + let globalMessage = + 'Usage of the Ember Global is deprecated. ' + + 'You should import the Ember module or the specific API instead.\n\n' + + 'See https://deprecations.emberjs.com/v3.x/#toc_ember-global for details.\n\n' + + 'Usages of the Ember Global may be caused by an outdated ember-cli-babel dependency. ' + + suggestions; + + if (hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all') { + globalMessage += + '\n### Important ###\n\n' + + 'In order to avoid repeatedly showing the same deprecation messages, ' + + 'no further deprecation messages will be shown for usages of the Ember Global ' + + 'until ember-cli-babel is upgraded to v7.26.6 or above.\n\n' + + 'To see all instances of this deprecation message, ' + + 'set the `EMBER_GLOBAL_DEPRECATIONS` environment variable to "all", ' + + 'e.g. `EMBER_GLOBAL_DEPRECATIONS=all ember test`.\n'; + } + + globalMessage += details; + + let onDotAccess = `function (dotKey, importKey, module) { + var message = + 'Using \`' + dotKey + '\` has been deprecated. Instead, import the value directly from ' + module + ':\\n\\n' + + ' import { ' + importKey + ' } from \\'' + module + '\\';\\n\\n' + + 'These usages may be caused by an outdated ember-cli-babel dependency. ' + + ${JSON.stringify(suggestions)}; + + if (${ + hasActionableSuggestions && + process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' + }) { + message += + '\\n### Important ###\\n\\n' + + 'In order to avoid repeatedly showing the same deprecation messages, ' + + 'no further deprecation messages will be shown for theses deprecated usages ' + + 'until ember-cli-babel is upgraded to v7.26.6 or above.\\n\\n' + + 'To see all instances of this deprecation message, ' + + 'set the \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS\` environment variable to "all", ' + + 'e.g. \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS=all ember test\`.\\n'; + } + + message += ${JSON.stringify(details)}; + + return message; + }`; + + let shouldIssueSingleDeprecation = + hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'; + + let bootstrap = `require('@ember/-internals/bootstrap').default()`; + + if (shouldIssueSingleDeprecation) { + bootstrap = ` + require('@ember/-internals/bootstrap').default( + ${JSON.stringify(globalMessage)}, + ${hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'} + ); + + (function(disabled, once, _onDotAccess) { + var onDotAccess = function () { + if (disabled) { + return null; + } else { + disabled = once; + return _onDotAccess.apply(undefined, arguments); + } + }; + + require('@ember/object')._onDotAccess(onDotAccess); + + require('@ember/runloop')._onDotAccess(onDotAccess); + })( + false, + ${ + hasActionableSuggestions && + process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' + }, + ${onDotAccess} + ); + `; + } + + return { + globalMessage, + hasActionableSuggestions, + shouldIssueSingleDeprecation, + bootstrap, + }; +}; diff --git a/lib/index.js b/lib/index.js index 8f0ff2ffc36..677c26ad73b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,7 +10,7 @@ const buildStripClassCallcheckPlugin = require('./build-strip-class-callcheck-pl const injectBabelHelpers = require('./transforms/inject-babel-helpers').injectBabelHelpers; const debugTree = require('broccoli-debug').buildDebugCallback('ember-source:addon'); const vmBabelPlugins = require('@glimmer/vm-babel-plugins'); -const semver = require('semver'); +const globalDeprecationInfo = require('./global-deprecation-utils'); const PRE_BUILT_TARGETS = [ 'last 1 Chrome versions', @@ -41,17 +41,6 @@ add( path.join(__dirname, '..', 'dist', 'ember-template-compiler.js') ); -function* walkAddonTree(project, pathToAddon = []) { - for (let addon of project.addons) { - yield [addon, pathToAddon]; - yield* walkAddonTree(addon, [...pathToAddon, `${addon.name}@${addon.pkg.version}`]); - } -} - -function requirementFor(pkg, deps = {}) { - return deps[pkg]; -} - module.exports = { init() { this._super.init && this._super.init.apply(this, arguments); @@ -76,13 +65,19 @@ module.exports = { name: 'ember-source', paths, absolutePaths, - _bootstrapEmber: "require('@ember/-internals/bootstrap').default();", _jqueryIntegrationEnabled: true, included() { this._super.included.apply(this, arguments); - this._issueGlobalsDeprecation(); + let { hasActionableSuggestions, globalMessage, bootstrap } = globalDeprecationInfo( + this.project + ); + this._bootstrapEmber = bootstrap; + + if (hasActionableSuggestions) { + this.ui.writeWarnLine('[DEPRECATION] ' + globalMessage); + } const { has } = require('@ember/edition-utils'); @@ -324,289 +319,4 @@ module.exports = { return debugTree(new MergeTrees([ember, templateCompiler, jquery]), 'vendor:final'); }, - - _issueGlobalsDeprecation() { - if (process.env.EMBER_ENV === 'production') { - return; - } - - let isYarnProject = ((root) => { - try { - // eslint-disable-next-line node/no-unpublished-require - return require('ember-cli/lib/utilities/is-yarn-project')(root); - } catch { - return undefined; - } - })(this.project.root); - - let groupedByTopLevelAddon = Object.create(null); - let groupedByVersion = Object.create(null); - let projectInfo; - - for (let [addon, pathToAddon] of walkAddonTree(this.project)) { - let version = addon.pkg.version; - - if (addon.name === 'ember-cli-babel' && semver.lt(version, '7.26.6')) { - let info; - - if (addon.parent === this.project) { - let requirement = requirementFor('ember-cli-babel', this.project.pkg.devDependencies); - let compatible = semver.satisfies('7.26.6', requirement); - - info = projectInfo = { - parent: `${this.project.name()} (your app)`, - version, - requirement, - compatible, - dormant: false, - path: pathToAddon, - }; - } else { - let requirement = requirementFor('ember-cli-babel', addon.parent.pkg.dependencies); - let compatible = semver.satisfies('7.26.6', requirement); - let dormant = addon.parent._fileSystemInfo - ? addon.parent._fileSystemInfo().hasJSFiles === false - : false; - - let topLevelAddon = addon.parent; - - while (topLevelAddon.parent !== this.project) { - topLevelAddon = topLevelAddon.parent; - } - - info = { - parent: `${addon.parent.name}@${addon.pkg.version}`, - version, - requirement, - compatible, - dormant, - path: pathToAddon, - }; - - let addons = groupedByTopLevelAddon[topLevelAddon.name] || []; - groupedByTopLevelAddon[topLevelAddon.name] = [...addons, info]; - } - - let group = groupedByVersion[version] || Object.create(null); - groupedByVersion[version] = group; - - let addons = group[info.parent] || []; - group[info.parent] = [...addons, info]; - } - } - - if (Object.keys(groupedByVersion).length === 0) { - return; - } - - let dormantTopLevelAddons = []; - let compatibleTopLevelAddons = []; - let incompatibleTopLevelAddons = []; - - for (let addon of Object.keys(groupedByTopLevelAddon)) { - let group = groupedByTopLevelAddon[addon]; - - if (group.every((info) => info.dormant)) { - dormantTopLevelAddons.push(addon); - } else if (group.every((info) => info.compatible)) { - compatibleTopLevelAddons.push(addon); - } else { - incompatibleTopLevelAddons.push(addon); - } - } - - let suggestions = 'The following steps may help:\n\n'; - - let hasActionableSuggestions = false; - - if (projectInfo) { - suggestions += '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; - hasActionableSuggestions = true; - } else if (compatibleTopLevelAddons.length > 0) { - // Only show the compatible addons if the project itself is up-to-date, because updating the - // project's own dependency on ember-cli-babel to latest may also get these addons to use it - // as well. Otherwise, there is an unnecessary copy in the tree and it needs to be deduped. - if (isYarnProject === true) { - suggestions += - '* Run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; - } else if (isYarnProject === false) { - suggestions += '* Run `npm dedupe`.\n'; - } else { - suggestions += - '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n' + - '* If using npm, run `npm dedupe`.\n'; - } - - hasActionableSuggestions = true; - } - - if (incompatibleTopLevelAddons.length > 0) { - suggestions += '* Upgrade the following addons to the latest version:\n'; - - for (let addon of incompatibleTopLevelAddons) { - suggestions += ` * ${addon}\n`; - } - - hasActionableSuggestions = true; - } - - if (!hasActionableSuggestions) { - // Only show the dormant addons if there are nothing else to do because they are unlikely to - // be the problem. - suggestions += '* Upgrade the following addons to the latest version, if available:\n'; - - for (let addon of dormantTopLevelAddons) { - suggestions += ` * ${addon}\n`; - } - } - - let details = - '\n### Details ###\n\n' + - 'Prior to v7.26.6, ember-cli-babel sometimes transpiled imports into the equivalent Ember Global API, ' + - 'potentially triggering this deprecation message indirectly, ' + - 'even when you did not observe these deprecated usages in your code.\n\n' + - 'The following outdated versions are found in your project:\n'; - - let hasDormantAddons = false; - let hasCompatibleAddons = false; - - for (let version of Object.keys(groupedByVersion).sort(semver.compare)) { - details += `\n* ember-cli-babel@${version}, currently used by:\n`; - - for (let parent of Object.keys(groupedByVersion[version]).sort()) { - let info = groupedByVersion[version][parent][0]; - - details += ` * ${parent}`; - - if (info.dormant) { - details += ' (Dormant)\n'; - hasDormantAddons = true; - } else if (info.compatible) { - details += ' (Compatible)\n'; - hasCompatibleAddons = true; - } else { - details += '\n'; - } - - details += ` * Depends on ember-cli-babel@${groupedByVersion[version][parent][0].requirement}\n`; - - for (let info of groupedByVersion[version][parent]) { - let adddedBy = info.path.slice(0, -1); - - if (adddedBy.length) { - details += ` * Added by ${adddedBy.join(' > ')}\n`; - } - - if (info.compatible) { - hasCompatibleAddons = true; - } - } - } - } - - if (hasDormantAddons) { - details += - '\nNote: Addons marked as "Dormant" does not appear to have any JavaScript files. ' + - 'Therefore, even if they are using an old version ember-cli-babel, they are ' + - 'unlikely to be the cuplrit of this deprecation and can likely be ignored.\n'; - } - - if (hasCompatibleAddons) { - details += `\nNote: Addons marked as "Compatible" are already compatible with ember-cli-babel@7.26.6. `; - - if (projectInfo) { - details += 'Try upgrading your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; - } else { - if (isYarnProject === true) { - details += - 'Try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; - } else if (isYarnProject === false) { - details += 'Try running `npm dedupe`.\n'; - } else { - details += - 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + - 'If using npm, try running `npm dedupe`.\n'; - } - } - } - - let globalMessage = - 'Usage of the Ember Global is deprecated. ' + - 'You should import the Ember module or the specific API instead.\n\n' + - 'See https://deprecations.emberjs.com/v3.x/#toc_ember-global for details.\n\n' + - 'Usages of the Ember Global may be caused by an outdated ember-cli-babel dependency. ' + - suggestions; - - if (hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all') { - globalMessage += - '\n### Important ###\n\n' + - 'In order to avoid repeatedly showing the same deprecation messages, ' + - 'no further deprecation messages will be shown for usages of the Ember Global ' + - 'until ember-cli-babel is upgraded to v7.26.6 or above.\n\n' + - 'To see all instances of this deprecation message, ' + - 'set the `EMBER_GLOBAL_DEPRECATIONS` environment variable to "all", ' + - 'e.g. `EMBER_GLOBAL_DEPRECATIONS=all ember test`.\n'; - } - - globalMessage += details; - - if (hasActionableSuggestions) { - this.ui.writeWarnLine('[DEPRECATION] ' + globalMessage); - } - - let onDotAccess = `function (dotKey, importKey, module) { - var message = - 'Using \`' + dotKey + '\` has been deprecated. Instead, import the value directly from ' + module + ':\\n\\n' + - ' import { ' + importKey + ' } from \\'' + module + '\\';\\n\\n' + - 'These usages may be caused by an outdated ember-cli-babel dependency. ' + - ${JSON.stringify(suggestions)}; - - if (${ - hasActionableSuggestions && - process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' - }) { - message += - '\\n### Important ###\\n\\n' + - 'In order to avoid repeatedly showing the same deprecation messages, ' + - 'no further deprecation messages will be shown for theses deprecated usages ' + - 'until ember-cli-babel is upgraded to v7.26.6 or above.\\n\\n' + - 'To see all instances of this deprecation message, ' + - 'set the \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS\` environment variable to "all", ' + - 'e.g. \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS=all ember test\`.\\n'; - } - - message += ${JSON.stringify(details)}; - - return message; - }`; - - this._bootstrapEmber = ` - require('@ember/-internals/bootstrap').default( - ${JSON.stringify(globalMessage)}, - ${hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'} - ); - - (function(disabled, once, _onDotAccess) { - var onDotAccess = function () { - if (disabled) { - return null; - } else { - disabled = once; - return _onDotAccess.apply(undefined, arguments); - } - }; - - require('@ember/object')._onDotAccess(onDotAccess); - - require('@ember/runloop')._onDotAccess(onDotAccess); - })( - false, - ${ - hasActionableSuggestions && - process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' - }, - ${onDotAccess} - ); - `; - }, }; From 540fafaff2eb33f89e3935d39ff1c93933e3f5a1 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 2 Jun 2021 17:22:44 -0400 Subject: [PATCH 2/4] Add some basic tests Co-authored-by: Stefan Penner --- lib/global-deprecation-utils.js | 64 +++---- tests/node/global-deprecation-info-test.js | 207 +++++++++++++++++++++ 2 files changed, 235 insertions(+), 36 deletions(-) create mode 100644 tests/node/global-deprecation-info-test.js diff --git a/lib/global-deprecation-utils.js b/lib/global-deprecation-utils.js index 6cf7980c08c..ce5c43e0352 100644 --- a/lib/global-deprecation-utils.js +++ b/lib/global-deprecation-utils.js @@ -1,4 +1,7 @@ +'use strict'; + const semver = require('semver'); +const validSemverRange = require('semver/ranges/valid'); function* walkAddonTree(project, pathToAddon = []) { for (let addon of project.addons) { @@ -11,19 +14,17 @@ function requirementFor(pkg, deps = {}) { return deps[pkg]; } -module.exports = function (project) { - if (process.env.EMBER_ENV === 'production') { - return; - } +const DEFAULT_RESULT = Object.freeze({ + globalMessage: '', + hasActionableSuggestions: false, + shouldIssueSingleDeprecation: false, + bootstrap: `require('@ember/-internals/bootstrap').default()`, +}); - let isYarnProject = ((root) => { - try { - // eslint-disable-next-line node/no-unpublished-require - return require('ember-cli/lib/utilities/is-yarn-project')(root); - } catch { - return undefined; - } - })(project.root); +module.exports = function (project, env = process.env) { + if (env.EMBER_ENV === 'production') { + return DEFAULT_RESULT; + } let groupedByTopLevelAddon = Object.create(null); let groupedByVersion = Object.create(null); @@ -36,8 +37,12 @@ module.exports = function (project) { let info; if (addon.parent === project) { - let requirement = requirementFor('ember-cli-babel', project.pkg.devDependencies); - let compatible = semver.satisfies('7.26.6', requirement); + let requirement = + requirementFor('ember-cli-babel', project.pkg.dependencies) || + requirementFor('ember-cli-babel', project.pkg.devDependencies); + + let validRange = validSemverRange(requirement); + let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; info = projectInfo = { parent: `${project.name()} (your app)`, @@ -49,7 +54,8 @@ module.exports = function (project) { }; } else { let requirement = requirementFor('ember-cli-babel', addon.parent.pkg.dependencies); - let compatible = semver.satisfies('7.26.6', requirement); + let validRange = validSemverRange(requirement); + let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; let dormant = addon.parent._fileSystemInfo ? addon.parent._fileSystemInfo().hasJSFiles === false : false; @@ -82,7 +88,7 @@ module.exports = function (project) { } if (Object.keys(groupedByVersion).length === 0) { - return; + return DEFAULT_RESULT; } let dormantTopLevelAddons = []; @@ -112,16 +118,9 @@ module.exports = function (project) { // Only show the compatible addons if the project itself is up-to-date, because updating the // project's own dependency on ember-cli-babel to latest may also get these addons to use it // as well. Otherwise, there is an unnecessary copy in the tree and it needs to be deduped. - if (isYarnProject === true) { - suggestions += - '* Run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; - } else if (isYarnProject === false) { - suggestions += '* Run `npm dedupe`.\n'; - } else { - suggestions += - '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n' + - '* If using npm, run `npm dedupe`.\n'; - } + suggestions += + '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n' + + '* If using npm, run `npm dedupe`.\n'; hasActionableSuggestions = true; } @@ -203,16 +202,9 @@ module.exports = function (project) { if (projectInfo) { details += 'Try upgrading your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; } else { - if (isYarnProject === true) { - details += - 'Try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n'; - } else if (isYarnProject === false) { - details += 'Try running `npm dedupe`.\n'; - } else { - details += - 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + - 'If using npm, try running `npm dedupe`.\n'; - } + details += + 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + + 'If using npm, try running `npm dedupe`.\n'; } } diff --git a/tests/node/global-deprecation-info-test.js b/tests/node/global-deprecation-info-test.js new file mode 100644 index 00000000000..2cea2139518 --- /dev/null +++ b/tests/node/global-deprecation-info-test.js @@ -0,0 +1,207 @@ +'use strict'; + +const globalDeprecationInfo = require('../../lib/global-deprecation-utils'); + +QUnit.module('globalDeprecationInfo', function (hooks) { + let project, env; + + function buildBabel(parent, version) { + return { + name: 'ember-cli-babel', + parent, + pkg: { + version, + }, + addons: [], + }; + } + + hooks.beforeEach(function () { + project = { + name() { + return 'fake-project'; + }, + pkg: { + dependencies: {}, + devDependencies: {}, + }, + addons: [], + }; + env = Object.create(null); + }); + + hooks.afterEach(function () {}); + + QUnit.test('when in production, does nothing', function (assert) { + env.EMBER_ENV = 'production'; + + let result = globalDeprecationInfo(project, env); + + assert.deepEqual(result, { + globalMessage: '', + hasActionableSuggestions: false, + shouldIssueSingleDeprecation: false, + bootstrap: `require('@ember/-internals/bootstrap').default()`, + }); + }); + + QUnit.test('without addons, does nothing', function (assert) { + project.addons = []; + let result = globalDeprecationInfo(project, env); + + assert.deepEqual(result, { + globalMessage: '', + hasActionableSuggestions: false, + shouldIssueSingleDeprecation: false, + bootstrap: `require('@ember/-internals/bootstrap').default()`, + }); + }); + + QUnit.test('projects own ember-cli-babel is too old', function (assert) { + project.pkg.devDependencies = { + 'ember-cli-babel': '^7.26.0', + }; + + project.addons.push({ + name: 'ember-cli-babel', + parent: project, + pkg: { + version: '7.26.5', + }, + addons: [], + }); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, true); + assert.strictEqual(result.hasActionableSuggestions, true); + assert.ok( + result.globalMessage.includes( + '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + ) + ); + }); + + QUnit.test('projects has ember-cli-babel in dependencies', function (assert) { + project.pkg.dependencies = { + 'ember-cli-babel': '^7.25.0', + }; + + project.addons.push({ + name: 'ember-cli-babel', + parent: project, + pkg: { + version: '7.26.5', + }, + addons: [], + }); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, true); + assert.strictEqual(result.hasActionableSuggestions, true); + assert.ok( + result.globalMessage.includes( + '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + ) + ); + }); + QUnit.test( + 'projects has no devDependencies, but old ember-cli-babel found in addons array', + function (assert) { + project.pkg.devDependencies = {}; + + project.addons.push({ + name: 'ember-cli-babel', + parent: project, + pkg: { + version: '7.26.5', + }, + addons: [], + }); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, true); + assert.strictEqual(result.hasActionableSuggestions, true); + assert.ok( + result.globalMessage.includes( + '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + ) + ); + } + ); + + QUnit.test('projects uses linked ember-cli-babel', function (assert) { + project.pkg.devDependencies = { + 'ember-cli-babel': 'link:./some/path/here', + }; + + let otherAddon = { + name: 'other-thing-here', + parent: project, + pkg: {}, + addons: [], + }; + + otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); + project.addons.push(buildBabel(project, '7.26.6'), otherAddon); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, true); + assert.strictEqual(result.hasActionableSuggestions, true); + + assert.ok( + result.globalMessage.includes( + '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel`' + ) + ); + assert.ok(result.globalMessage.includes('* If using npm, run `npm dedupe`')); + }); + + QUnit.test('projects own ember-cli-babel is up to date', function (assert) { + project.pkg.devDependencies = { + 'ember-cli-babel': '^7.26.0', + }; + + project.addons.push({ + name: 'ember-cli-babel', + parent: project, + pkg: { + version: '7.26.6', + }, + addons: [], + }); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, false); + assert.strictEqual(result.hasActionableSuggestions, false); + assert.notOk( + result.globalMessage.includes( + '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + ) + ); + }); + + QUnit.test('transient babel that is out of date', function (assert) { + project.pkg.devDependencies = { + 'ember-cli-babel': '^7.26.0', + }; + + let otherAddon = { + name: 'other-thing-here', + parent: project, + pkg: { + dependencies: { + 'ember-cli-babel': '^7.25.0', + }, + }, + addons: [], + }; + + otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); + project.addons.push(buildBabel(project, '7.26.6'), otherAddon); + + let result = globalDeprecationInfo(project, env); + assert.strictEqual(result.shouldIssueSingleDeprecation, true); + assert.strictEqual(result.hasActionableSuggestions, true); + assert.ok(result.globalMessage.includes('* other-thing-here@7.26.5 (Compatible)')); + }); +}); From fffe619e2fe2de4ee603b2ccb2dbf33192f9f51d Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 2 Jun 2021 18:33:26 -0400 Subject: [PATCH 3/4] Refactor `@ember/-internals/bootstrap` back into an IIFE Fixes a few things: * Ensures that Embroider has a consistent protocol for ensuring the global is bootstrapped * Allows our override code to be transpiled * Ensures that any bootstrapping tweaks force us out of "prebuilt" mode (allowing that customized bootstrap code to actually run) Co-authored-by: Stefan Penner --- lib/global-deprecation-utils.js | 45 ++------------ lib/index.js | 60 +++++++++++++++++-- package.json | 2 +- packages/@ember/-internals/bootstrap/index.js | 10 ++-- .../-internals/bootstrap/lib/overrides.js | 9 +++ 5 files changed, 75 insertions(+), 51 deletions(-) create mode 100644 packages/@ember/-internals/bootstrap/lib/overrides.js diff --git a/lib/global-deprecation-utils.js b/lib/global-deprecation-utils.js index ce5c43e0352..a4532b7a015 100644 --- a/lib/global-deprecation-utils.js +++ b/lib/global-deprecation-utils.js @@ -18,7 +18,6 @@ const DEFAULT_RESULT = Object.freeze({ globalMessage: '', hasActionableSuggestions: false, shouldIssueSingleDeprecation: false, - bootstrap: `require('@ember/-internals/bootstrap').default()`, }); module.exports = function (project, env = process.env) { @@ -228,7 +227,7 @@ module.exports = function (project, env = process.env) { globalMessage += details; - let onDotAccess = `function (dotKey, importKey, module) { + let dotAccessOverride = ` var message = 'Using \`' + dotKey + '\` has been deprecated. Instead, import the value directly from ' + module + ':\\n\\n' + ' import { ' + importKey + ' } from \\'' + module + '\\';\\n\\n' + @@ -250,50 +249,18 @@ module.exports = function (project, env = process.env) { } message += ${JSON.stringify(details)}; - - return message; - }`; + `; let shouldIssueSingleDeprecation = hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'; - let bootstrap = `require('@ember/-internals/bootstrap').default()`; - - if (shouldIssueSingleDeprecation) { - bootstrap = ` - require('@ember/-internals/bootstrap').default( - ${JSON.stringify(globalMessage)}, - ${hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'} - ); - - (function(disabled, once, _onDotAccess) { - var onDotAccess = function () { - if (disabled) { - return null; - } else { - disabled = once; - return _onDotAccess.apply(undefined, arguments); - } - }; - - require('@ember/object')._onDotAccess(onDotAccess); - - require('@ember/runloop')._onDotAccess(onDotAccess); - })( - false, - ${ - hasActionableSuggestions && - process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' - }, - ${onDotAccess} - ); - `; - } - return { globalMessage, hasActionableSuggestions, shouldIssueSingleDeprecation, - bootstrap, + + dotAccessOverride, }; }; + +module.exports.DEFAULT_RESULT = DEFAULT_RESULT; diff --git a/lib/index.js b/lib/index.js index 677c26ad73b..aa4cf07a4ef 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,6 +11,7 @@ const injectBabelHelpers = require('./transforms/inject-babel-helpers').injectBa const debugTree = require('broccoli-debug').buildDebugCallback('ember-source:addon'); const vmBabelPlugins = require('@glimmer/vm-babel-plugins'); const globalDeprecationInfo = require('./global-deprecation-utils'); +const createFile = require('broccoli-file-creator'); const PRE_BUILT_TARGETS = [ 'last 1 Chrome versions', @@ -70,10 +71,50 @@ module.exports = { included() { this._super.included.apply(this, arguments); - let { hasActionableSuggestions, globalMessage, bootstrap } = globalDeprecationInfo( - this.project - ); - this._bootstrapEmber = bootstrap; + let { + hasActionableSuggestions, + globalMessage, + shouldIssueSingleDeprecation, + + dotAccessOverride, + } = globalDeprecationInfo(this.project); + + if (hasActionableSuggestions) { + let shouldIssueSingleDotAccessDeprecation = + hasActionableSuggestions && + process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all'; + + this._bootstrapMessageOverrideTree = createFile( + 'packages/@ember/-internals/bootstrap/lib/overrides.js', + + `export const message = ${JSON.stringify(globalMessage)}; + export const deprecateOnce = ${shouldIssueSingleDeprecation}; + + import { _onDotAccess as runloopDotAccessOverride } from '@ember/runloop'; + import { _onDotAccess as objectDotAccessOverride } from '@ember/object'; + + export function setupDotAccess() { + let disabled = false; + let once = ${shouldIssueSingleDotAccessDeprecation}; + + function onDotAccess(dotKey, importKey, module) { + if (disabled) { + return null; + } else { + disabled = once; + + ${dotAccessOverride}; + + return message; + } + } + + runloopDotAccessOverride(onDotAccess); + objectDotAccessOverride(onDotAccess); + } + ` + ); + } if (hasActionableSuggestions) { this.ui.writeWarnLine('[DEPRECATION] ' + globalMessage); @@ -219,6 +260,10 @@ module.exports = { }, buildEmberBundles(tree, isProduction) { + if (this._bootstrapMessageOverrideTree) { + tree = new MergeTrees([tree, this._bootstrapMessageOverrideTree], { overwrite: true }); + } + let packages = this.transpileTree(new Funnel(tree, { srcDir: 'packages' }), isProduction); let dependencies = this.transpileTree( @@ -254,7 +299,7 @@ module.exports = { return new MergeTrees([ concatBundle(emberFiles, { outputFile: 'ember.js', - footer: this._bootstrapEmber, + footer: `require('@ember/-internals/bootstrap')`, }), concatBundle(emberTestingFiles, { @@ -299,10 +344,13 @@ module.exports = { if ( !isProduction && + // do the running applications targets match our prebuilt assets targets? PRE_BUILT_TARGETS.every((target) => targets.includes(target)) && targets.length === PRE_BUILT_TARGETS.length && // if node is defined in targets we can't reliably use the prebuilt bundles - !targetNode + !targetNode && + // if we have a custom override for bootstrapping (e.g. for globals deprecations) we can't use the prebuilt bundles + !this._bootstrapMessageOverrideTree ) { ember = new Funnel(tree, { destDir: 'ember', diff --git a/package.json b/package.json index e07dee3245e..bfcd7f3feab 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "babel-plugin-filter-imports": "^4.0.0", "broccoli-concat": "^4.2.4", "broccoli-debug": "^0.6.4", + "broccoli-file-creator": "^2.1.1", "broccoli-funnel": "^2.0.2", "broccoli-merge-trees": "^4.2.0", "chalk": "^4.0.0", @@ -98,7 +99,6 @@ "babel-template": "^6.26.0", "backburner.js": "^2.7.0", "broccoli-babel-transpiler": "^7.8.0", - "broccoli-file-creator": "^2.1.1", "broccoli-persistent-filter": "^2.3.1", "broccoli-plugin": "^4.0.3", "broccoli-rollup": "^2.1.1", diff --git a/packages/@ember/-internals/bootstrap/index.js b/packages/@ember/-internals/bootstrap/index.js index 05e70d3a721..60fee640296 100644 --- a/packages/@ember/-internals/bootstrap/index.js +++ b/packages/@ember/-internals/bootstrap/index.js @@ -1,11 +1,11 @@ import require from 'require'; import { context } from '@ember/-internals/environment'; import { deprecate } from '@ember/debug'; +import { message, deprecateOnce, setupDotAccess } from './lib/overrides'; -const DEFAULT_MESSAGE = - 'Usage of the Ember Global is deprecated. You should import the Ember module or the specific API instead.'; +setupDotAccess(); -export default function bootstrap(message = DEFAULT_MESSAGE, once = false) { +(function bootstrap() { let Ember; let disabled = false; @@ -28,7 +28,7 @@ export default function bootstrap(message = DEFAULT_MESSAGE, once = false) { }, }); - if (once) { + if (deprecateOnce) { disabled = true; } @@ -47,4 +47,4 @@ export default function bootstrap(message = DEFAULT_MESSAGE, once = false) { // eslint-disable-next-line no-undef module.exports = Ember = require('ember').default; } -} +})(); diff --git a/packages/@ember/-internals/bootstrap/lib/overrides.js b/packages/@ember/-internals/bootstrap/lib/overrides.js new file mode 100644 index 00000000000..38af9cd1cc1 --- /dev/null +++ b/packages/@ember/-internals/bootstrap/lib/overrides.js @@ -0,0 +1,9 @@ +// these are default values that are overriden in some cases by the ember-addon entry point code +// in lib/index.js (see `included` and `buildEmberBundles` methods there for more context) + +export const message = + 'Usage of the Ember Global is deprecated. You should import the Ember module or the specific API instead.'; + +export const deprecateOnce = false; + +export function setupDotAccess() {} From 35689f9c4fe27f810895aeeec1ebb5ef5700ce38 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Thu, 3 Jun 2021 02:05:48 -0700 Subject: [PATCH 4/4] Refactor Overrides infrastructure, add more tests Also fixed a few minor errors in the message (wrong version number etc) Included a list of hardcoded known dormant addons (polyfills mostly) to make the suggestions more reliable. --- lib/global-deprecation-utils.js | 266 ------ lib/index.js | 61 +- lib/overrides.js | 411 +++++++++ packages/@ember/-internals/bootstrap/index.js | 50 - packages/@ember/-internals/bootstrap/index.ts | 64 ++ .../-internals/bootstrap/lib/overrides.js | 9 - .../@ember/-internals/overrides/index.d.ts | 12 + packages/@ember/-internals/overrides/index.js | 3 + packages/@ember/object/index.js | 11 +- packages/@ember/runloop/index.js | 13 +- tests/node/fixtures/project.js | 148 +++ tests/node/global-deprecation-info-test.js | 207 ----- tests/node/overrides-test.js | 869 ++++++++++++++++++ 13 files changed, 1527 insertions(+), 597 deletions(-) delete mode 100644 lib/global-deprecation-utils.js create mode 100644 lib/overrides.js delete mode 100644 packages/@ember/-internals/bootstrap/index.js create mode 100644 packages/@ember/-internals/bootstrap/index.ts delete mode 100644 packages/@ember/-internals/bootstrap/lib/overrides.js create mode 100644 packages/@ember/-internals/overrides/index.d.ts create mode 100644 packages/@ember/-internals/overrides/index.js create mode 100644 tests/node/fixtures/project.js delete mode 100644 tests/node/global-deprecation-info-test.js create mode 100644 tests/node/overrides-test.js diff --git a/lib/global-deprecation-utils.js b/lib/global-deprecation-utils.js deleted file mode 100644 index a4532b7a015..00000000000 --- a/lib/global-deprecation-utils.js +++ /dev/null @@ -1,266 +0,0 @@ -'use strict'; - -const semver = require('semver'); -const validSemverRange = require('semver/ranges/valid'); - -function* walkAddonTree(project, pathToAddon = []) { - for (let addon of project.addons) { - yield [addon, pathToAddon]; - yield* walkAddonTree(addon, [...pathToAddon, `${addon.name}@${addon.pkg.version}`]); - } -} - -function requirementFor(pkg, deps = {}) { - return deps[pkg]; -} - -const DEFAULT_RESULT = Object.freeze({ - globalMessage: '', - hasActionableSuggestions: false, - shouldIssueSingleDeprecation: false, -}); - -module.exports = function (project, env = process.env) { - if (env.EMBER_ENV === 'production') { - return DEFAULT_RESULT; - } - - let groupedByTopLevelAddon = Object.create(null); - let groupedByVersion = Object.create(null); - let projectInfo; - - for (let [addon, pathToAddon] of walkAddonTree(project)) { - let version = addon.pkg.version; - - if (addon.name === 'ember-cli-babel' && semver.lt(version, '7.26.6')) { - let info; - - if (addon.parent === project) { - let requirement = - requirementFor('ember-cli-babel', project.pkg.dependencies) || - requirementFor('ember-cli-babel', project.pkg.devDependencies); - - let validRange = validSemverRange(requirement); - let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; - - info = projectInfo = { - parent: `${project.name()} (your app)`, - version, - requirement, - compatible, - dormant: false, - path: pathToAddon, - }; - } else { - let requirement = requirementFor('ember-cli-babel', addon.parent.pkg.dependencies); - let validRange = validSemverRange(requirement); - let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; - let dormant = addon.parent._fileSystemInfo - ? addon.parent._fileSystemInfo().hasJSFiles === false - : false; - - let topLevelAddon = addon.parent; - - while (topLevelAddon.parent !== project) { - topLevelAddon = topLevelAddon.parent; - } - - info = { - parent: `${addon.parent.name}@${addon.pkg.version}`, - version, - requirement, - compatible, - dormant, - path: pathToAddon, - }; - - let addons = groupedByTopLevelAddon[topLevelAddon.name] || []; - groupedByTopLevelAddon[topLevelAddon.name] = [...addons, info]; - } - - let group = groupedByVersion[version] || Object.create(null); - groupedByVersion[version] = group; - - let addons = group[info.parent] || []; - group[info.parent] = [...addons, info]; - } - } - - if (Object.keys(groupedByVersion).length === 0) { - return DEFAULT_RESULT; - } - - let dormantTopLevelAddons = []; - let compatibleTopLevelAddons = []; - let incompatibleTopLevelAddons = []; - - for (let addon of Object.keys(groupedByTopLevelAddon)) { - let group = groupedByTopLevelAddon[addon]; - - if (group.every((info) => info.dormant)) { - dormantTopLevelAddons.push(addon); - } else if (group.every((info) => info.compatible)) { - compatibleTopLevelAddons.push(addon); - } else { - incompatibleTopLevelAddons.push(addon); - } - } - - let suggestions = 'The following steps may help:\n\n'; - - let hasActionableSuggestions = false; - - if (projectInfo) { - suggestions += '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; - hasActionableSuggestions = true; - } else if (compatibleTopLevelAddons.length > 0) { - // Only show the compatible addons if the project itself is up-to-date, because updating the - // project's own dependency on ember-cli-babel to latest may also get these addons to use it - // as well. Otherwise, there is an unnecessary copy in the tree and it needs to be deduped. - suggestions += - '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.\n' + - '* If using npm, run `npm dedupe`.\n'; - - hasActionableSuggestions = true; - } - - if (incompatibleTopLevelAddons.length > 0) { - suggestions += '* Upgrade the following addons to the latest version:\n'; - - for (let addon of incompatibleTopLevelAddons) { - suggestions += ` * ${addon}\n`; - } - - hasActionableSuggestions = true; - } - - if (!hasActionableSuggestions) { - // Only show the dormant addons if there are nothing else to do because they are unlikely to - // be the problem. - suggestions += '* Upgrade the following addons to the latest version, if available:\n'; - - for (let addon of dormantTopLevelAddons) { - suggestions += ` * ${addon}\n`; - } - } - - let details = - '\n### Details ###\n\n' + - 'Prior to v7.26.6, ember-cli-babel sometimes transpiled imports into the equivalent Ember Global API, ' + - 'potentially triggering this deprecation message indirectly, ' + - 'even when you did not observe these deprecated usages in your code.\n\n' + - 'The following outdated versions are found in your project:\n'; - - let hasDormantAddons = false; - let hasCompatibleAddons = false; - - for (let version of Object.keys(groupedByVersion).sort(semver.compare)) { - details += `\n* ember-cli-babel@${version}, currently used by:\n`; - - for (let parent of Object.keys(groupedByVersion[version]).sort()) { - let info = groupedByVersion[version][parent][0]; - - details += ` * ${parent}`; - - if (info.dormant) { - details += ' (Dormant)\n'; - hasDormantAddons = true; - } else if (info.compatible) { - details += ' (Compatible)\n'; - hasCompatibleAddons = true; - } else { - details += '\n'; - } - - details += ` * Depends on ember-cli-babel@${groupedByVersion[version][parent][0].requirement}\n`; - - for (let info of groupedByVersion[version][parent]) { - let adddedBy = info.path.slice(0, -1); - - if (adddedBy.length) { - details += ` * Added by ${adddedBy.join(' > ')}\n`; - } - - if (info.compatible) { - hasCompatibleAddons = true; - } - } - } - } - - if (hasDormantAddons) { - details += - '\nNote: Addons marked as "Dormant" does not appear to have any JavaScript files. ' + - 'Therefore, even if they are using an old version ember-cli-babel, they are ' + - 'unlikely to be the cuplrit of this deprecation and can likely be ignored.\n'; - } - - if (hasCompatibleAddons) { - details += `\nNote: Addons marked as "Compatible" are already compatible with ember-cli-babel@7.26.6. `; - - if (projectInfo) { - details += 'Try upgrading your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; - } else { - details += - 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + - 'If using npm, try running `npm dedupe`.\n'; - } - } - - let globalMessage = - 'Usage of the Ember Global is deprecated. ' + - 'You should import the Ember module or the specific API instead.\n\n' + - 'See https://deprecations.emberjs.com/v3.x/#toc_ember-global for details.\n\n' + - 'Usages of the Ember Global may be caused by an outdated ember-cli-babel dependency. ' + - suggestions; - - if (hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all') { - globalMessage += - '\n### Important ###\n\n' + - 'In order to avoid repeatedly showing the same deprecation messages, ' + - 'no further deprecation messages will be shown for usages of the Ember Global ' + - 'until ember-cli-babel is upgraded to v7.26.6 or above.\n\n' + - 'To see all instances of this deprecation message, ' + - 'set the `EMBER_GLOBAL_DEPRECATIONS` environment variable to "all", ' + - 'e.g. `EMBER_GLOBAL_DEPRECATIONS=all ember test`.\n'; - } - - globalMessage += details; - - let dotAccessOverride = ` - var message = - 'Using \`' + dotKey + '\` has been deprecated. Instead, import the value directly from ' + module + ':\\n\\n' + - ' import { ' + importKey + ' } from \\'' + module + '\\';\\n\\n' + - 'These usages may be caused by an outdated ember-cli-babel dependency. ' + - ${JSON.stringify(suggestions)}; - - if (${ - hasActionableSuggestions && - process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all' - }) { - message += - '\\n### Important ###\\n\\n' + - 'In order to avoid repeatedly showing the same deprecation messages, ' + - 'no further deprecation messages will be shown for theses deprecated usages ' + - 'until ember-cli-babel is upgraded to v7.26.6 or above.\\n\\n' + - 'To see all instances of this deprecation message, ' + - 'set the \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS\` environment variable to "all", ' + - 'e.g. \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS=all ember test\`.\\n'; - } - - message += ${JSON.stringify(details)}; - `; - - let shouldIssueSingleDeprecation = - hasActionableSuggestions && process.env.EMBER_GLOBAL_DEPRECATIONS !== 'all'; - - return { - globalMessage, - hasActionableSuggestions, - shouldIssueSingleDeprecation, - - dotAccessOverride, - }; -}; - -module.exports.DEFAULT_RESULT = DEFAULT_RESULT; diff --git a/lib/index.js b/lib/index.js index aa4cf07a4ef..ce887fbeb22 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,8 +10,7 @@ const buildStripClassCallcheckPlugin = require('./build-strip-class-callcheck-pl const injectBabelHelpers = require('./transforms/inject-babel-helpers').injectBabelHelpers; const debugTree = require('broccoli-debug').buildDebugCallback('ember-source:addon'); const vmBabelPlugins = require('@glimmer/vm-babel-plugins'); -const globalDeprecationInfo = require('./global-deprecation-utils'); -const createFile = require('broccoli-file-creator'); +const Overrides = require('./overrides'); const PRE_BUILT_TARGETS = [ 'last 1 Chrome versions', @@ -67,57 +66,19 @@ module.exports = { paths, absolutePaths, _jqueryIntegrationEnabled: true, + _overrideTree: undefined, included() { this._super.included.apply(this, arguments); - let { - hasActionableSuggestions, - globalMessage, - shouldIssueSingleDeprecation, + let overrides = Overrides.for(this.project); - dotAccessOverride, - } = globalDeprecationInfo(this.project); - - if (hasActionableSuggestions) { - let shouldIssueSingleDotAccessDeprecation = - hasActionableSuggestions && - process.env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS !== 'all'; - - this._bootstrapMessageOverrideTree = createFile( - 'packages/@ember/-internals/bootstrap/lib/overrides.js', - - `export const message = ${JSON.stringify(globalMessage)}; - export const deprecateOnce = ${shouldIssueSingleDeprecation}; - - import { _onDotAccess as runloopDotAccessOverride } from '@ember/runloop'; - import { _onDotAccess as objectDotAccessOverride } from '@ember/object'; - - export function setupDotAccess() { - let disabled = false; - let once = ${shouldIssueSingleDotAccessDeprecation}; - - function onDotAccess(dotKey, importKey, module) { - if (disabled) { - return null; - } else { - disabled = once; - - ${dotAccessOverride}; - - return message; - } - } - - runloopDotAccessOverride(onDotAccess); - objectDotAccessOverride(onDotAccess); - } - ` - ); + if (overrides.hasOverrides) { + this._overrideTree = overrides.toTree(); } - if (hasActionableSuggestions) { - this.ui.writeWarnLine('[DEPRECATION] ' + globalMessage); + if (overrides.hasBuildTimeWarning) { + this.ui.writeWarnLine('[DEPRECATION] ' + overrides.buildTimeWarning); } const { has } = require('@ember/edition-utils'); @@ -260,8 +221,8 @@ module.exports = { }, buildEmberBundles(tree, isProduction) { - if (this._bootstrapMessageOverrideTree) { - tree = new MergeTrees([tree, this._bootstrapMessageOverrideTree], { overwrite: true }); + if (this._overrideTree) { + tree = new MergeTrees([tree, this._overrideTree], { overwrite: true }); } let packages = this.transpileTree(new Funnel(tree, { srcDir: 'packages' }), isProduction); @@ -349,8 +310,8 @@ module.exports = { targets.length === PRE_BUILT_TARGETS.length && // if node is defined in targets we can't reliably use the prebuilt bundles !targetNode && - // if we have a custom override for bootstrapping (e.g. for globals deprecations) we can't use the prebuilt bundles - !this._bootstrapMessageOverrideTree + // if we have a custom override (e.g. for globals deprecations) we can't use the prebuilt bundles + !this._overrideTree ) { ember = new Funnel(tree, { destDir: 'ember', diff --git a/lib/overrides.js b/lib/overrides.js new file mode 100644 index 00000000000..9eb67acaf56 --- /dev/null +++ b/lib/overrides.js @@ -0,0 +1,411 @@ +const createFile = require('broccoli-file-creator'); +const semver = require('semver'); +const validSemverRange = require('semver/ranges/valid'); + +const DEFAULT_OPTIONS = Object.freeze({ + showAllEmberGlobalDeprecations: false, + showAllDotAccessDeprecations: false, +}); + +function* walkAddonTree(project, pathToAddon = []) { + for (let addon of project.addons) { + yield [addon, pathToAddon]; + yield* walkAddonTree(addon, [...pathToAddon, `${addon.name}@${addon.pkg.version}`]); + } +} + +function requirementFor(pkg, deps = {}) { + return deps[pkg]; +} + +const KNWON_DORMANT_ADDONS = Object.freeze([ + 'ember-angle-bracket-invocation-polyfill', + 'ember-fn-helper-polyfill', + 'ember-in-element-polyfill', + 'ember-named-blocks-polyfill', + 'ember-on-modifier', +]); +module.exports = class Overrides { + static for(project, env = process.env) { + if (env.EMBER_ENV === 'production') { + return new Overrides([]); + } else { + return new Overrides(Overrides.addonsInfoFor(project), { + showAllEmberGlobalDeprecations: env.EMBER_GLOBAL_DEPRECATIONS === 'all', + showAllDotAccessDeprecations: + env.EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS === 'all', + }); + } + } + + static *addonsInfoFor(project) { + for (let [addon, pathToAddon] of walkAddonTree(project)) { + let version = addon.pkg.version; + + if (addon.name === 'ember-cli-babel' && semver.lt(version, '7.26.6')) { + if (addon.parent === project) { + let requirement = + requirementFor('ember-cli-babel', project.pkg.dependencies) || + requirementFor('ember-cli-babel', project.pkg.devDependencies); + + let validRange = validSemverRange(requirement); + let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; + + yield { + parent: `${project.name()} (your app)`, + topLevel: null, + version, + requirement, + compatible, + dormant: false, + path: [], + }; + } else { + let requirement = requirementFor('ember-cli-babel', addon.parent.pkg.dependencies); + let validRange = validSemverRange(requirement); + let compatible = validRange ? semver.satisfies('7.26.6', requirement) : true; + let dormant = + KNWON_DORMANT_ADDONS.includes(addon.parent.name) || + (addon.parent._fileSystemInfo + ? addon.parent._fileSystemInfo().hasJSFiles === false + : false); + + let topLevelAddon = addon.parent; + + while (topLevelAddon.parent !== project) { + topLevelAddon = topLevelAddon.parent; + } + + yield { + parent: `${addon.parent.name}@${addon.parent.pkg.version}`, + topLevel: topLevelAddon.name, + version, + requirement, + compatible, + dormant, + path: pathToAddon, + }; + } + } + } + } + + static printList(list, indent = '') { + let output = ''; + + for (let item of list) { + if (Array.isArray(item)) { + output += `${indent}* ${item[0]}\n`; + output += Overrides.printList(item[1], indent + ' '); + } else { + output += `${indent}* ${item}\n`; + } + } + + return output; + } + + constructor(addonsInfo, options = {}) { + let _addonsInfo = []; + let projectInfo; + let groupedByTopLevel = Object.create(null); + let groupedByVersion = Object.create(null); + + for (let info of addonsInfo) { + _addonsInfo.push(info); + + if (info.topLevel === null) { + projectInfo = info; + } else { + let topLevel = info.topLevel; + let addons = groupedByTopLevel[topLevel] || []; + groupedByTopLevel[topLevel] = [...addons, info]; + } + + let version = info.version; + + let group = groupedByVersion[version] || Object.create(null); + groupedByVersion[version] = group; + + let addons = group[info.parent] || []; + group[info.parent] = [...addons, info]; + } + + let dormantTopLevelAddons = []; + let compatibleTopLevelAddons = []; + let incompatibleTopLevelAddons = []; + + let suggestions = []; + let hasActionableSuggestions = false; + + if (_addonsInfo.length > 0) { + for (let addon of Object.keys(groupedByTopLevel)) { + let group = groupedByTopLevel[addon]; + + if (group.every((info) => info.dormant)) { + dormantTopLevelAddons.push(addon); + } else if (group.every((info) => info.compatible)) { + compatibleTopLevelAddons.push(addon); + } else { + incompatibleTopLevelAddons.push(addon); + } + } + + if (projectInfo) { + suggestions.push('Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`.'); + hasActionableSuggestions = true; + } else if (compatibleTopLevelAddons.length > 0) { + // Only show the compatible addons if the project itself is up-to-date, because updating the + // project's own dependency on ember-cli-babel to latest may also get these addons to use it + // as well. Otherwise, there is an unnecessary copy in the tree and it needs to be deduped. + suggestions.push( + 'If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + ); + + suggestions.push('If using npm, run `npm dedupe`.'); + + hasActionableSuggestions = true; + } + + if (incompatibleTopLevelAddons.length > 0) { + suggestions.push([ + 'Upgrade the following addons to the latest version:', + incompatibleTopLevelAddons, + ]); + + hasActionableSuggestions = true; + } + + if (!hasActionableSuggestions) { + suggestions.push([ + 'Upgrade the following addons to the latest version, if available:', + dormantTopLevelAddons, + ]); + } + } + + this.addonsInfo = _addonsInfo; + this.projectInfo = projectInfo; + this.groupedByTopLevel = groupedByTopLevel; + this.groupedByVersion = groupedByVersion; + this.dormantTopLevelAddons = dormantTopLevelAddons; + this.compatibleTopLevelAddons = compatibleTopLevelAddons; + this.incompatibleTopLevelAddons = incompatibleTopLevelAddons; + this.suggestions = suggestions; + this.hasActionableSuggestions = hasActionableSuggestions; + this.options = { ...DEFAULT_OPTIONS, ...options }; + } + + get hasOverrides() { + return this.addonsInfo.length > 0; + } + + get hasBuildTimeWarning() { + return this.hasActionableSuggestions; + } + + get hasCompatibleAddons() { + return this.addonsInfo.some((info) => info.compatible); + } + + get hasDormantAddons() { + return this.addonsInfo.some((info) => info.dormant); + } + + get showAllEmberGlobalDeprecations() { + return !this.hasActionableSuggestions || this.options.showAllEmberGlobalDeprecations; + } + + get showAllDotAccessDeprecations() { + return !this.hasActionableSuggestions || this.options.showAllDotAccessDeprecations; + } + + get details() { + let details = + '\n### Details ###\n\n' + + 'Prior to v7.26.6, ember-cli-babel sometimes transpiled imports into the equivalent Ember Global API, ' + + 'potentially triggering this deprecation message indirectly, ' + + 'even when you did not observe these deprecated usages in your code.\n\n' + + 'The following outdated versions are found in your project:\n\n' + + Overrides.printList(this.outdated); + + if (this.hasDormantAddons) { + details += + '\nNote: Addons marked as "Dormant" does not appear to have any JavaScript files. ' + + 'Therefore, even if they are using an old version ember-cli-babel, they are ' + + 'unlikely to be the cuplrit of this deprecation and can likely be ignored.\n'; + } + + if (this.hasCompatibleAddons) { + details += `\nNote: Addons marked as "Compatible" are already compatible with ember-cli-babel@7.26.6. `; + + if (this.projectInfo) { + details += 'Try upgrading your `devDependencies` on `ember-cli-babel` to `^7.26.6`.\n'; + } else { + details += + 'If using yarn, try running `npx yarn-deduplicate --packages ember-cli-babel` followed by `yarn install`.' + + 'If using npm, try running `npm dedupe`.\n'; + } + } + + return details; + } + + get outdated() { + let list = []; + + let groupedByVersion = this.groupedByVersion; + + for (let version of Object.keys(groupedByVersion).sort(semver.compare)) { + let addons = []; + + for (let parent of Object.keys(groupedByVersion[version]).sort()) { + let info = groupedByVersion[version][parent][0]; + let addon; + let details = []; + + if (info.dormant) { + addon = `${parent} (Dormant)`; + } else if (info.compatible) { + addon = `${parent} (Compatible)`; + } else { + addon = parent; + } + + details.push(`Depends on ember-cli-babel@${info.requirement}`); + + for (let info of groupedByVersion[version][parent]) { + let adddedBy = info.path.slice(0, -1); + + if (adddedBy.length) { + details.push(`Added by ${adddedBy.join(' > ')}`); + } + } + + addons.push([addon, details]); + } + + list.push([`ember-cli-babel@${version}, currently used by:`, addons]); + } + + return list; + } + + get globalMessage() { + let message = + 'Usage of the Ember Global is deprecated. ' + + 'You should import the Ember module or the specific API instead.\n\n' + + 'See https://deprecations.emberjs.com/v3.x/#toc_ember-global for details.\n\n' + + 'Usages of the Ember Global may be caused by an outdated ember-cli-babel dependency. ' + + 'The following steps may help:\n\n' + + Overrides.printList(this.suggestions); + + if (!this.showAllEmberGlobalDeprecations) { + message += + '\n### Important ###\n\n' + + 'In order to avoid repeatedly showing the same deprecation messages, ' + + 'no further deprecation messages will be shown for usages of the Ember Global ' + + 'until ember-cli-babel is upgraded to v7.26.6 or above.\n\n' + + 'To see all instances of this deprecation message, ' + + 'set the `EMBER_GLOBAL_DEPRECATIONS` environment variable to "all", ' + + 'e.g. `EMBER_GLOBAL_DEPRECATIONS=all ember test`.\n'; + } + + message += this.details; + + return message; + } + + get buildTimeWarning() { + if (this.hasBuildTimeWarning) { + return `[DEPRECATION] ${this.globalMessage}`; + } else { + return ''; + } + } + + toTree() { + if (this.hasOverrides) { + return createFile('packages/@ember/-internals/overrides/index.js', this.toModule()); + } + } + + toModule() { + return ` + export let onEmberGlobalAccess; + export let onComputedDotAccess; + export let onRunloopDotAccess; + + ${this.toJS()}; + `; + } + + toJS() { + return ` + function once(callback) { + let called = false; + + return (...args) => { + if (called) { + return null; + } else { + called = true; + return callback(...args); + } + }; + } + + ${this.onDotAcces} + + onEmberGlobalAccess = ${this.onEmberGlobalAccess}; + onComputedDotAccess = onDotAccess; + onRunloopDotAccess = onDotAccess; + + if (!${this.showAllEmberGlobalDeprecations}) { + onEmberGlobalAccess = once(onEmberGlobalAccess); + } + + if (!${this.showAllDotAccessDeprecations}) { + onComputedDotAccess = once(onComputedDotAccess); + onRunloopDotAccess = once(onRunloopDotAccess); + } + `; + } + + get onEmberGlobalAccess() { + return ` + function onEmberGlobalAccess() { + return ${JSON.stringify(this.globalMessage)}; + } + `; + } + + get onDotAcces() { + return ` + function onDotAccess(dotKey, importKey, module) { + let message = + 'Using \`' + dotKey + '\` has been deprecated. Instead, import the value directly from ' + module + ':\\n\\n' + + ' import { ' + importKey + ' } from \\'' + module + '\\';\\n\\n' + + 'These usages may be caused by an outdated ember-cli-babel dependency. ' + + 'Usages of the Ember Global may be caused by an outdated ember-cli-babel dependency. ' + + 'The following steps may help:\\n\\n' + + ${JSON.stringify(Overrides.printList(this.suggestions))}; + + if (!${this.showAllDotAccessDeprecations}) { + message += + '\\n### Important ###\\n\\n' + + 'In order to avoid repeatedly showing the same deprecation messages, ' + + 'no further deprecation messages will be shown for theses deprecated usages ' + + 'until ember-cli-babel is upgraded to v7.26.6 or above.\\n\\n' + + 'To see all instances of this deprecation message, ' + + 'set the \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS\` environment variable to "all", ' + + 'e.g. \`EMBER_RUNLOOP_AND_COMPUTED_DOT_ACCESS_DEPRECATIONS=all ember test\`.\\n'; + } + + message += ${JSON.stringify(this.details)}; + + return message; + } + `; + } +}; diff --git a/packages/@ember/-internals/bootstrap/index.js b/packages/@ember/-internals/bootstrap/index.js deleted file mode 100644 index 60fee640296..00000000000 --- a/packages/@ember/-internals/bootstrap/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import require from 'require'; -import { context } from '@ember/-internals/environment'; -import { deprecate } from '@ember/debug'; -import { message, deprecateOnce, setupDotAccess } from './lib/overrides'; - -setupDotAccess(); - -(function bootstrap() { - let Ember; - let disabled = false; - - function defineEmber(key) { - Object.defineProperty(context.exports, key, { - enumerable: true, - configurable: true, - get() { - if (!Ember) { - Ember = require('ember').default; - } - - deprecate(message, disabled, { - id: 'ember-global', - until: '4.0.0', - url: 'https://deprecations.emberjs.com/v3.x/#toc_ember-global', - for: 'ember-source', - since: { - enabled: '3.27.0', - }, - }); - - if (deprecateOnce) { - disabled = true; - } - - return Ember; - }, - }); - } - - // Bootstrap the global - defineEmber('Ember'); - defineEmber('Em'); - - // Bootstrap Node module - // eslint-disable-next-line no-undef - if (typeof module === 'object' && typeof module.require === 'function') { - // eslint-disable-next-line no-undef - module.exports = Ember = require('ember').default; - } -})(); diff --git a/packages/@ember/-internals/bootstrap/index.ts b/packages/@ember/-internals/bootstrap/index.ts new file mode 100644 index 00000000000..022ef158407 --- /dev/null +++ b/packages/@ember/-internals/bootstrap/index.ts @@ -0,0 +1,64 @@ +import { context } from '@ember/-internals/environment'; +import { onEmberGlobalAccess } from '@ember/-internals/overrides'; +import { deprecate } from '@ember/debug'; +import { DEBUG } from '@glimmer/env'; +import require from 'require'; + +(function bootstrap() { + let Ember: unknown; + + let get = () => { + if (!Ember) { + // tslint:disable-next-line: no-require-imports + Ember = require('ember').default; + } + + return Ember; + }; + + if (DEBUG) { + let defaultHandler = () => { + return 'Usage of the Ember Global is deprecated. You should import the Ember module or the specific API instead.'; + }; + + let handler = onEmberGlobalAccess || defaultHandler; + let _get = get; + + get = () => { + let message = handler(); + + if (message !== null) { + deprecate(message, false, { + id: 'ember-global', + until: '4.0.0', + url: 'https://deprecations.emberjs.com/v3.x/#toc_ember-global', + for: 'ember-source', + since: { + enabled: '3.27.0', + }, + }); + } + + return _get(); + }; + } + + function defineEmber(key: string) { + Object.defineProperty(context.exports, key, { + enumerable: true, + configurable: true, + get, + }); + } + + // Bootstrap the global + defineEmber('Ember'); + defineEmber('Em'); + + // Bootstrap Node module + // eslint-disable-next-line no-undef + if (typeof module === 'object' && typeof module.require === 'function') { + // tslint:disable-next-line: no-require-imports + module.exports = Ember = require('ember').default; + } +})(); diff --git a/packages/@ember/-internals/bootstrap/lib/overrides.js b/packages/@ember/-internals/bootstrap/lib/overrides.js deleted file mode 100644 index 38af9cd1cc1..00000000000 --- a/packages/@ember/-internals/bootstrap/lib/overrides.js +++ /dev/null @@ -1,9 +0,0 @@ -// these are default values that are overriden in some cases by the ember-addon entry point code -// in lib/index.js (see `included` and `buildEmberBundles` methods there for more context) - -export const message = - 'Usage of the Ember Global is deprecated. You should import the Ember module or the specific API instead.'; - -export const deprecateOnce = false; - -export function setupDotAccess() {} diff --git a/packages/@ember/-internals/overrides/index.d.ts b/packages/@ember/-internals/overrides/index.d.ts new file mode 100644 index 00000000000..e52fbb81445 --- /dev/null +++ b/packages/@ember/-internals/overrides/index.d.ts @@ -0,0 +1,12 @@ +type Callback = (...args: TArgs) => TReturn; + +/** + * Returns a deprecation message or null to skip the deprecation. + */ +type Handler = Callback; +type GlobalAccessHandler = Handler<[]>; +type DotAccessHandler = Handler<[dotKey: string, importKey: string, module: string]>; + +export let onEmberGlobalAccess: GlobalAccessHandler | undefined; +export let onComputedDotAccess: DotAccessHandler | undefined; +export let onRunloopDotAccess: DotAccessHandler | undefined; diff --git a/packages/@ember/-internals/overrides/index.js b/packages/@ember/-internals/overrides/index.js new file mode 100644 index 00000000000..35b83669272 --- /dev/null +++ b/packages/@ember/-internals/overrides/index.js @@ -0,0 +1,3 @@ +export let onEmberGlobalAccess; +export let onComputedDotAccess; +export let onRunloopDotAccess; diff --git a/packages/@ember/object/index.js b/packages/@ember/object/index.js index af4dd9d5735..d6ae7fbbf84 100644 --- a/packages/@ember/object/index.js +++ b/packages/@ember/object/index.js @@ -2,6 +2,7 @@ import { DEBUG } from '@glimmer/env'; import { assert, deprecate } from '@ember/debug'; import { assign } from '@ember/polyfills'; import { isElementDescriptor, setClassicDecorator } from '@ember/-internals/metal'; +import { onComputedDotAccess } from '@ember/-internals/overrides'; export { Object as default } from '@ember/-internals/runtime'; @@ -55,22 +56,18 @@ import { uniq, } from '@ember/object/computed'; -export let _onDotAccess; - // eslint-disable-next-line no-undef if (DEBUG) { - let _callback = (dotKey, importKey, module) => { + let defaultHandler = (dotKey, importKey, module) => { return `Using \`${dotKey}\` has been deprecated. Instead, import the value directly from ${module}:\n\n import { ${importKey} } from '${module}';`; }; - _onDotAccess = (callback) => { - _callback = callback; - }; + let handler = onComputedDotAccess || defaultHandler; let defineDeprecatedComputedFunc = (key, func) => { Object.defineProperty(computed, key, { get() { - let message = _callback(`computed.${key}`, key, '@ember/object/computed'); + let message = handler(`computed.${key}`, key, '@ember/object/computed'); deprecate(message, message === null, { id: 'deprecated-run-loop-and-computed-dot-access', diff --git a/packages/@ember/runloop/index.js b/packages/@ember/runloop/index.js index 6ce847c96db..c78ceaa48a5 100644 --- a/packages/@ember/runloop/index.js +++ b/packages/@ember/runloop/index.js @@ -2,6 +2,7 @@ import { DEBUG } from '@glimmer/env'; import { assert, deprecate } from '@ember/debug'; import { onErrorTarget } from '@ember/-internals/error-handling'; import { flushAsyncObservers } from '@ember/-internals/metal'; +import { onRunloopDotAccess } from '@ember/-internals/overrides'; import Backburner from 'backburner'; let currentRunLoop = null; @@ -743,22 +744,18 @@ export function throttle() { export let _deprecatedGlobalGetCurrentRunLoop; -export let _onDotAccess; - // eslint-disable-next-line no-undef if (DEBUG) { - let _callback = (dotKey, importKey, module) => { + let defaultHandler = (dotKey, importKey, module) => { return `Using \`${dotKey}\` has been deprecated. Instead, import the value directly from ${module}:\n\n import { ${importKey} } from '${module}';`; }; - _onDotAccess = (callback) => { - _callback = callback; - }; + let handler = onRunloopDotAccess || defaultHandler; let defineDeprecatedRunloopFunc = (key, func) => { Object.defineProperty(run, key, { get() { - let message = _callback(`run.${key}`, key, '@ember/runloop'); + let message = handler(`run.${key}`, key, '@ember/runloop'); deprecate(message, message === null, { id: 'deprecated-run-loop-and-computed-dot-access', @@ -775,7 +772,7 @@ if (DEBUG) { }; _deprecatedGlobalGetCurrentRunLoop = () => { - let message = _callback('run.currentRunLoop', 'getCurrentRunLoop', '@ember/runloop'); + let message = handler('run.currentRunLoop', 'getCurrentRunLoop', '@ember/runloop'); deprecate(message, message === null, { id: 'deprecated-run-loop-and-computed-dot-access', diff --git a/tests/node/fixtures/project.js b/tests/node/fixtures/project.js new file mode 100644 index 00000000000..bcc867af91c --- /dev/null +++ b/tests/node/fixtures/project.js @@ -0,0 +1,148 @@ +module.exports = class Project { + static withDep(depOptions = {}, projectOptions = {}) { + let addons = projectOptions.addons || []; + + return new Project({ + ...projectOptions, + addons: [...addons, new Addon(depOptions)], + }); + } + + static withTransientDep(transientDepOptions = {}, depOptions = {}, projectOptions = {}) { + let addons = depOptions.addons || []; + + return Project.withDep( + { + ...depOptions, + addons: [ + ...addons, + new Addon({ + name: 'my-nested-addon', + version: '0.1.0', + ...transientDepOptions, + }), + ], + }, + projectOptions + ); + } + + constructor({ + name = 'my-app', + emberCliBabel, + dependencies = {}, + devDependencies = {}, + addons = [], + } = {}) { + this.name = () => name; + this.parent = null; + this.pkg = { + name, + dependencies: { ...dependencies }, + devDependencies: { ...devDependencies }, + }; + this.addons = [...addons]; + + if (typeof emberCliBabel === 'string') { + this.pkg.devDependencies['ember-cli-babel'] = emberCliBabel; + } + + reifyAddons(this); + addMissingAddons(this, this.pkg.devDependencies); + addMissingAddons(this, this.pkg.dependencies); + addMissingDeps(this, true); + } +}; + +class Addon { + constructor({ + parent, + name = 'my-addon', + version = '1.0.0', + emberCliBabel, + dependencies = {}, + addons = [], + hasJSFiles = name !== 'ember-cli-babel', + } = {}) { + this.parent = parent; + this.name = name; + this.pkg = { + name, + version, + dependencies: { ...dependencies }, + devDependencies: {}, + }; + this.addons = [...addons]; + this._fileSystemInfo = () => ({ hasJSFiles }); + + if (typeof emberCliBabel === 'string') { + this.pkg.dependencies['ember-cli-babel'] = emberCliBabel; + } + + reifyAddons(this); + addMissingAddons(this, this.pkg.dependencies); + addMissingDeps(this); + } +} + +// Can only handle the few hardcoded cases +function resolve(requirement) { + if (requirement.startsWith('link:')) { + // Expecting something like "link:1.2.3" + return requirement.slice(5); + } else { + // Only handles "^1.0.0" -> "1.0.0", doesn't work for the general case + return requirement.slice(1); + } +} + +function reifyAddons(parent) { + parent.addons = parent.addons.map((addon) => { + if (addon instanceof Addon) { + addon.parent = parent; + return addon; + } else { + let version = addon.version; + + if (!version) { + if (parent.pkg.devDependencies[addon.name]) { + version = resolve(parent.pkg.devDependencies[addon.name]); + } else if (parent.pkg.dependencies[addon.name]) { + version = resolve(parent.pkg.dependencies[addon.name]); + } + } + + return new Addon({ ...addon, parent, version }); + } + }); +} + +function addMissingAddons(parent, deps) { + for (let [name, requirement] of Object.entries(deps)) { + if (!parent.addons.find((addon) => addon.name === name)) { + parent.addons.push( + new Addon({ + parent, + name, + version: resolve(requirement), + }) + ); + } + } +} + +function addMissingDeps(parent, devDeps = false) { + for (let addon of parent.addons) { + let target = parent.pkg.dependencies; + let isMissing = !(addon.name in target); + + if (devDeps) { + target = parent.pkg.devDependencies; + isMissing = isMissing && !(addon.name in target); + } + + if (isMissing) { + target[addon.name] = `^${addon.pkg.version}`; + } + } +} diff --git a/tests/node/global-deprecation-info-test.js b/tests/node/global-deprecation-info-test.js deleted file mode 100644 index 2cea2139518..00000000000 --- a/tests/node/global-deprecation-info-test.js +++ /dev/null @@ -1,207 +0,0 @@ -'use strict'; - -const globalDeprecationInfo = require('../../lib/global-deprecation-utils'); - -QUnit.module('globalDeprecationInfo', function (hooks) { - let project, env; - - function buildBabel(parent, version) { - return { - name: 'ember-cli-babel', - parent, - pkg: { - version, - }, - addons: [], - }; - } - - hooks.beforeEach(function () { - project = { - name() { - return 'fake-project'; - }, - pkg: { - dependencies: {}, - devDependencies: {}, - }, - addons: [], - }; - env = Object.create(null); - }); - - hooks.afterEach(function () {}); - - QUnit.test('when in production, does nothing', function (assert) { - env.EMBER_ENV = 'production'; - - let result = globalDeprecationInfo(project, env); - - assert.deepEqual(result, { - globalMessage: '', - hasActionableSuggestions: false, - shouldIssueSingleDeprecation: false, - bootstrap: `require('@ember/-internals/bootstrap').default()`, - }); - }); - - QUnit.test('without addons, does nothing', function (assert) { - project.addons = []; - let result = globalDeprecationInfo(project, env); - - assert.deepEqual(result, { - globalMessage: '', - hasActionableSuggestions: false, - shouldIssueSingleDeprecation: false, - bootstrap: `require('@ember/-internals/bootstrap').default()`, - }); - }); - - QUnit.test('projects own ember-cli-babel is too old', function (assert) { - project.pkg.devDependencies = { - 'ember-cli-babel': '^7.26.0', - }; - - project.addons.push({ - name: 'ember-cli-babel', - parent: project, - pkg: { - version: '7.26.5', - }, - addons: [], - }); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, true); - assert.strictEqual(result.hasActionableSuggestions, true); - assert.ok( - result.globalMessage.includes( - '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' - ) - ); - }); - - QUnit.test('projects has ember-cli-babel in dependencies', function (assert) { - project.pkg.dependencies = { - 'ember-cli-babel': '^7.25.0', - }; - - project.addons.push({ - name: 'ember-cli-babel', - parent: project, - pkg: { - version: '7.26.5', - }, - addons: [], - }); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, true); - assert.strictEqual(result.hasActionableSuggestions, true); - assert.ok( - result.globalMessage.includes( - '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' - ) - ); - }); - QUnit.test( - 'projects has no devDependencies, but old ember-cli-babel found in addons array', - function (assert) { - project.pkg.devDependencies = {}; - - project.addons.push({ - name: 'ember-cli-babel', - parent: project, - pkg: { - version: '7.26.5', - }, - addons: [], - }); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, true); - assert.strictEqual(result.hasActionableSuggestions, true); - assert.ok( - result.globalMessage.includes( - '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' - ) - ); - } - ); - - QUnit.test('projects uses linked ember-cli-babel', function (assert) { - project.pkg.devDependencies = { - 'ember-cli-babel': 'link:./some/path/here', - }; - - let otherAddon = { - name: 'other-thing-here', - parent: project, - pkg: {}, - addons: [], - }; - - otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); - project.addons.push(buildBabel(project, '7.26.6'), otherAddon); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, true); - assert.strictEqual(result.hasActionableSuggestions, true); - - assert.ok( - result.globalMessage.includes( - '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel`' - ) - ); - assert.ok(result.globalMessage.includes('* If using npm, run `npm dedupe`')); - }); - - QUnit.test('projects own ember-cli-babel is up to date', function (assert) { - project.pkg.devDependencies = { - 'ember-cli-babel': '^7.26.0', - }; - - project.addons.push({ - name: 'ember-cli-babel', - parent: project, - pkg: { - version: '7.26.6', - }, - addons: [], - }); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, false); - assert.strictEqual(result.hasActionableSuggestions, false); - assert.notOk( - result.globalMessage.includes( - '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' - ) - ); - }); - - QUnit.test('transient babel that is out of date', function (assert) { - project.pkg.devDependencies = { - 'ember-cli-babel': '^7.26.0', - }; - - let otherAddon = { - name: 'other-thing-here', - parent: project, - pkg: { - dependencies: { - 'ember-cli-babel': '^7.25.0', - }, - }, - addons: [], - }; - - otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); - project.addons.push(buildBabel(project, '7.26.6'), otherAddon); - - let result = globalDeprecationInfo(project, env); - assert.strictEqual(result.shouldIssueSingleDeprecation, true); - assert.strictEqual(result.hasActionableSuggestions, true); - assert.ok(result.globalMessage.includes('* other-thing-here@7.26.5 (Compatible)')); - }); -}); diff --git a/tests/node/overrides-test.js b/tests/node/overrides-test.js new file mode 100644 index 00000000000..90dc4aebe4e --- /dev/null +++ b/tests/node/overrides-test.js @@ -0,0 +1,869 @@ +'use strict'; + +const Project = require('./fixtures/project'); +const Overrides = require('../../lib/overrides'); + +function cmp(a, b) { + if (a == undefined || a < b) { + return -1; + } else if (b == undefined || a > b) { + return 1; + } else { + return 0; + } +} + +function addonsInfoFor(project) { + if (!(project instanceof Project)) { + project = new Project(project); + } + + let addonsInfo = [...Overrides.addonsInfoFor(project)]; + + addonsInfo.sort((a, b) => cmp(a.topLevel, b.topLevel) || cmp(a.parent, b.parent)); + + return addonsInfo; +} + +function fullExample() { + return { + name: 'direwolf', + devDependencies: { + 'active-model-adapter': '^2.2.0', + 'ember-animated': '^0.11.0', + 'ember-cli-babel': '^7.26.3', + 'ember-fetch': '^8.0.5', + 'ember-source': 'link:3.27.3', + }, + addons: [ + { + name: 'active-model-adapter', + dependencies: { + 'ember-cli-babel': '^6.18.0', + }, + }, + { + name: 'ember-animated', + dependencies: { + 'ember-angle-bracket-invocation-polyfill': '^2.0.0', + 'ember-cli-babel': '^7.26.3', + }, + addons: [ + { + name: 'ember-angle-bracket-invocation-polyfill', + dependencies: { + 'ember-cli-babel': '^6.16.0', + }, + addons: [ + { + name: 'ember-cli-babel', + version: '6.18.0', + }, + ], + }, + { + name: 'ember-cli-babel', + version: '7.26.5', + }, + ], + }, + { + name: 'ember-cli-babel', + version: '7.26.5', + }, + { + name: 'ember-fetch', + hasJSFiles: false, + dependencies: { + 'ember-cli-babel': '^7.26.0', + }, + }, + { + name: 'ember-source', + dependencies: { + 'ember-cli-babel': '^7.26.6', + }, + }, + ], + }; +} + +function infoForApp({ + name = 'direwolf', + version = '7.26.6', + requirement = `^${version}`, + compatible = requirement.startsWith('^7'), +} = {}) { + return { + parent: `${name} (your app)`, + topLevel: null, + version, + requirement, + compatible, + dormant: false, + path: [], + }; +} + +// function info({ +// parent, +// topLevel = parent, +// version = '7.26.6', +// requirement = `^${version}`, +// compatible = requirement.startsWith('^7'), +// dormant = false, +// path = [`${parent}@${version}`], +// } = {}) { +// return { +// parent, +// topLevel, +// version, +// requirement, +// compatible, +// dormant, +// path, +// }; +// } + +function evalJS(overrides) { + return eval(` + (function () { + let onEmberGlobalAccess, onComputedDotAccess, onRunloopDotAccess; + + ${overrides.toJS()} + + return { + onEmberGlobalAccess: onEmberGlobalAccess, + onComputedDotAccess: onComputedDotAccess, + onRunloopDotAccess: onRunloopDotAccess, + }; + })() + `); +} + +QUnit.module('Overrides', function () { + QUnit.module('.addonsInfoFor', function () { + // app + + QUnit.test('it returns old babel added by app', function (assert) { + assert.deepEqual(addonsInfoFor({ emberCliBabel: '^6.0.0' }), [ + { + parent: 'my-app (your app)', + topLevel: null, + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: false, + path: [], + }, + ]); + }); + + QUnit.test('it returns old but compatible babel added by app', function (assert) { + assert.deepEqual(addonsInfoFor({ emberCliBabel: '^7.0.0' }), [ + { + parent: 'my-app (your app)', + topLevel: null, + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: false, + path: [], + }, + ]); + }); + + QUnit.test('it does not return new babel added by app', function (assert) { + assert.deepEqual(addonsInfoFor({ emberCliBabel: '^7.26.6' }), []); + }); + + // direct dependency + + QUnit.test('it returns old babel added by a dependency', function (assert) { + assert.deepEqual(addonsInfoFor(Project.withDep({ emberCliBabel: '^6.0.0' })), [ + { + parent: 'my-addon@1.0.0', + topLevel: 'my-addon', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: false, + path: ['my-addon@1.0.0'], + }, + ]); + }); + + QUnit.test('it returns old but compatible babel added by a dependency', function (assert) { + assert.deepEqual(addonsInfoFor(Project.withDep({ emberCliBabel: '^7.0.0' })), [ + { + parent: 'my-addon@1.0.0', + topLevel: 'my-addon', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: false, + path: ['my-addon@1.0.0'], + }, + ]); + }); + + QUnit.test('it does not return new babel added by a dependency', function (assert) { + assert.deepEqual(addonsInfoFor(Project.withDep({ emberCliBabel: '^7.26.6' })), []); + }); + + // direct dependency (dormant) + + QUnit.test('it returns old babel added by a dormant dependency', function (assert) { + assert.deepEqual( + addonsInfoFor(Project.withDep({ emberCliBabel: '^6.0.0', hasJSFiles: false })), + [ + { + parent: 'my-addon@1.0.0', + topLevel: 'my-addon', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: true, + path: ['my-addon@1.0.0'], + }, + ] + ); + }); + + QUnit.test('it returns old but compatible babel added by a dormant dependency', function ( + assert + ) { + assert.deepEqual( + addonsInfoFor(Project.withDep({ emberCliBabel: '^7.0.0', hasJSFiles: false })), + [ + { + parent: 'my-addon@1.0.0', + topLevel: 'my-addon', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: true, + path: ['my-addon@1.0.0'], + }, + ] + ); + }); + + QUnit.test('it does not return new babel added by a dormant dependency', function (assert) { + assert.deepEqual( + addonsInfoFor(Project.withDep({ emberCliBabel: '^7.26.6', hasJSFiles: false })), + [] + ); + }); + + // transient dep + + QUnit.test('it returns old babel added by a transient dependency', function (assert) { + assert.deepEqual(addonsInfoFor(Project.withTransientDep({ emberCliBabel: '^6.0.0' })), [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: false, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ]); + }); + + QUnit.test('it returns old but compatible babel added by a transient dependency', function ( + assert + ) { + assert.deepEqual(addonsInfoFor(Project.withTransientDep({ emberCliBabel: '^7.0.0' })), [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: false, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ]); + }); + + QUnit.test('it does not return new babel added by a transient dependency', function (assert) { + assert.deepEqual(addonsInfoFor(Project.withDep({ emberCliBabel: '^7.26.6' })), []); + }); + + // dormant transient dep + + QUnit.test('it returns old babel added by a dormant transient dependency', function (assert) { + assert.deepEqual( + addonsInfoFor(Project.withTransientDep({ emberCliBabel: '^6.0.0', hasJSFiles: false })), + [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: true, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ] + ); + }); + + QUnit.test( + 'it returns old but compatible babel added by a dormant transient dependency', + function (assert) { + assert.deepEqual( + addonsInfoFor(Project.withTransientDep({ emberCliBabel: '^7.0.0', hasJSFiles: false })), + [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: true, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ] + ); + } + ); + + QUnit.test('it does not return new babel added by a dormant transient dependency', function ( + assert + ) { + assert.deepEqual( + addonsInfoFor(Project.withDep({ emberCliBabel: '^7.26.6', hasJSFiles: false })), + [] + ); + }); + + // transient dep through a dormant dep + + QUnit.test( + 'it returns old babel added by a transient dependency through a dormant dependency', + function (assert) { + assert.deepEqual( + addonsInfoFor( + Project.withTransientDep({ emberCliBabel: '^6.0.0' }, { hasJSFiles: false }) + ), + [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: false, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ] + ); + } + ); + + QUnit.test( + 'it returns old but compatible babel added by a transient dependency through a dormant dependency', + function (assert) { + assert.deepEqual( + addonsInfoFor( + Project.withTransientDep({ emberCliBabel: '^7.0.0' }, { hasJSFiles: false }) + ), + [ + { + parent: 'my-nested-addon@0.1.0', + topLevel: 'my-addon', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: false, + path: ['my-addon@1.0.0', 'my-nested-addon@0.1.0'], + }, + ] + ); + } + ); + + QUnit.test( + 'it does not return new babel added by a transient dependency through a dormant dependency', + function (assert) { + assert.deepEqual( + addonsInfoFor(Project.withDep({ emberCliBabel: '^7.26.6' }, { hasJSFiles: false })), + [] + ); + } + ); + + // linked dep + + QUnit.test('it returns old babel added by a linked dependency', function (assert) { + assert.deepEqual( + addonsInfoFor( + new Project({ + devDependencies: { + 'ember-source': 'link:3.27.3', + }, + addons: [ + { + name: 'ember-source', + emberCliBabel: '^6.0.0', + }, + ], + }) + ), + [ + { + parent: 'ember-source@3.27.3', + topLevel: 'ember-source', + version: '6.0.0', + requirement: '^6.0.0', + compatible: false, + dormant: false, + path: ['ember-source@3.27.3'], + }, + ] + ); + }); + + QUnit.test('it returns old but compatible babel added by a linked dependency', function ( + assert + ) { + assert.deepEqual( + addonsInfoFor( + new Project({ + devDependencies: { + 'ember-source': 'link:3.27.3', + }, + addons: [ + { + name: 'ember-source', + emberCliBabel: '^7.0.0', + }, + ], + }) + ), + [ + { + parent: 'ember-source@3.27.3', + topLevel: 'ember-source', + version: '7.0.0', + requirement: '^7.0.0', + compatible: true, + dormant: false, + path: ['ember-source@3.27.3'], + }, + ] + ); + }); + + QUnit.test('it does not return new babel added by a linked dependency', function (assert) { + assert.deepEqual( + addonsInfoFor( + new Project({ + devDependencies: { + 'ember-source': 'link:3.27.3', + }, + addons: [ + { + name: 'ember-source', + emberCliBabel: '^7.26.6', + }, + ], + }) + ), + [] + ); + }); + + // full example + + QUnit.test('full example', function (assert) { + let project = new Project(fullExample()); + + assert.deepEqual(addonsInfoFor(project), [ + { + parent: 'direwolf (your app)', + topLevel: null, + version: '7.26.5', + requirement: '^7.26.3', + compatible: true, + dormant: false, + path: [], + }, + { + parent: 'active-model-adapter@2.2.0', + topLevel: 'active-model-adapter', + version: '6.18.0', + requirement: '^6.18.0', + compatible: false, + dormant: false, + path: ['active-model-adapter@2.2.0'], + }, + { + parent: 'ember-angle-bracket-invocation-polyfill@2.0.0', + topLevel: 'ember-animated', + version: '6.18.0', + requirement: '^6.16.0', + compatible: false, + dormant: true, + path: ['ember-animated@0.11.0', 'ember-angle-bracket-invocation-polyfill@2.0.0'], + }, + { + parent: 'ember-animated@0.11.0', + topLevel: 'ember-animated', + version: '7.26.5', + requirement: '^7.26.3', + compatible: true, + dormant: false, + path: ['ember-animated@0.11.0'], + }, + { + parent: 'ember-fetch@8.0.5', + topLevel: 'ember-fetch', + version: '7.26.0', + requirement: '^7.26.0', + compatible: true, + dormant: true, + path: ['ember-fetch@8.0.5'], + }, + ]); + }); + }); + + QUnit.module('.printList', function () { + QUnit.test('it can print a flat list', function (assert) { + assert.equal( + Overrides.printList(['first', 'second', 'third'], ' '), + `\ + * first + * second + * third +` + ); + }); + + QUnit.test('it can print a nested list', function (assert) { + assert.equal( + Overrides.printList( + [ + 'first', + [ + 'second', + ['second.1', ['second.2', ['second.2.1', 'second.2.2', 'second.2.3']], 'second.3'], + ], + 'third', + ], + ' ' + ), + `\ + * first + * second + * second.1 + * second.2 + * second.2.1 + * second.2.2 + * second.2.3 + * second.3 + * third +` + ); + }); + }); + + QUnit.test('it does nothing when in production', function (assert) { + let project = new Project(fullExample()); + let overrides = Overrides.for(project, { EMBER_ENV: 'production' }); + + assert.strictEqual(overrides.hasOverrides, false, 'hasOverrides'); + assert.strictEqual(overrides.hasBuildTimeWarning, false, 'hasBuildTimeWarning'); + }); + + QUnit.test('it does nothing when everything is on new babel', function (assert) { + let overrides = new Overrides([]); + + assert.strictEqual(overrides.hasOverrides, false, 'hasOverrides'); + assert.strictEqual(overrides.hasBuildTimeWarning, false, 'hasBuildTimeWarning'); + }); + + QUnit.test('when app is on old babel', function (assert) { + let overrides = new Overrides([infoForApp({ version: '6.0.0' })]); + + assert.strictEqual(overrides.hasOverrides, true, 'hasOverrides'); + assert.strictEqual(overrides.hasBuildTimeWarning, true, 'hasBuildTimeWarning'); + assert.strictEqual(overrides.hasActionableSuggestions, true, 'hasActionableSuggestions'); + assert.strictEqual(overrides.hasCompatibleAddons, false, 'hasCompatibleAddons'); + assert.strictEqual(overrides.hasDormantAddons, false, 'hasDormantAddons'); + assert.strictEqual( + overrides.showAllEmberGlobalDeprecations, + false, + 'showAllEmberGlobalDeprecations' + ); + assert.strictEqual( + overrides.showAllDotAccessDeprecations, + false, + 'showAllDotAccessDeprecations' + ); + assert.deepEqual(overrides.suggestions, [ + 'Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`.', + ]); + assert.equal( + overrides.outdated.length, + 1 /* number of different old babel versions */, + 'outdated.length' + ); + assert.ok( + overrides.buildTimeWarning.startsWith( + '[DEPRECATION] Usage of the Ember Global is deprecated.' + ), + 'overrides.buildTimeWarning' + ); + assert.ok( + overrides.globalMessage.startsWith('Usage of the Ember Global is deprecated.'), + 'overrides.globalMessage' + ); + + let { onEmberGlobalAccess, onComputedDotAccess, onRunloopDotAccess } = evalJS(overrides); + + assert.equal( + onEmberGlobalAccess(), + overrides.globalMessage, + 'onEmberGlobalAccess() (first call)' + ); + + assert.strictEqual(onEmberGlobalAccess(), null, 'onEmberGlobalAccess() (second call)'); + + assert.ok( + onComputedDotAccess('computed.reads', 'reads', '@ember/object/computed').startsWith( + 'Using `computed.reads` has been deprecated. ' + + 'Instead, import the value directly from @ember/object/computed:\n\n' + + " import { reads } from '@ember/object/computed';\n\n" + ), + 'onComputedDotAccess() (first call)' + ); + + assert.strictEqual( + onComputedDotAccess('computed.reads', 'reads', '@ember/object/computed'), + null, + 'onComputedDotAccess() (second call)' + ); + + assert.ok( + onRunloopDotAccess('run.next', 'next', '@ember/runloop').startsWith( + 'Using `run.next` has been deprecated. ' + + 'Instead, import the value directly from @ember/runloop:\n\n' + + " import { next } from '@ember/runloop';\n\n" + ), + 'onRunloopDotAccess() (first call)' + ); + + assert.strictEqual( + onRunloopDotAccess('run.next', 'next', '@ember/runloop'), + null, + 'onRunloopDotAccess() (second call)' + ); + }); + + // let project, env; + + // function buildBabel(parent, version) { + // return { + // name: 'ember-cli-babel', + // parent, + // pkg: { + // version, + // }, + // addons: [], + // }; + // } + + // hooks.beforeEach(function () { + // project = { + // name() { + // return 'fake-project'; + // }, + // pkg: { + // dependencies: {}, + // devDependencies: {}, + // }, + // addons: [], + // }; + // env = Object.create(null); + // }); + + // hooks.afterEach(function () {}); + + // QUnit.test('when in production, does nothing', function (assert) { + // env.EMBER_ENV = 'production'; + + // let result = globalDeprecationInfo(project, env); + + // assert.deepEqual(result, { + // globalMessage: '', + // hasActionableSuggestions: false, + // shouldIssueSingleDeprecation: false, + // bootstrap: `require('@ember/-internals/bootstrap').default()`, + // }); + // }); + + // QUnit.test('without addons, does nothing', function (assert) { + // project.addons = []; + // let result = globalDeprecationInfo(project, env); + + // assert.deepEqual(result, { + // globalMessage: '', + // hasActionableSuggestions: false, + // shouldIssueSingleDeprecation: false, + // bootstrap: `require('@ember/-internals/bootstrap').default()`, + // }); + // }); + + // QUnit.test('projects own ember-cli-babel is too old', function (assert) { + // project.pkg.devDependencies = { + // 'ember-cli-babel': '^7.26.0', + // }; + + // project.addons.push({ + // name: 'ember-cli-babel', + // parent: project, + // pkg: { + // version: '7.26.5', + // }, + // addons: [], + // }); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, true); + // assert.strictEqual(result.hasActionableSuggestions, true); + // assert.ok( + // result.globalMessage.includes( + // '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + // ) + // ); + // }); + + // QUnit.test('projects has ember-cli-babel in dependencies', function (assert) { + // project.pkg.dependencies = { + // 'ember-cli-babel': '^7.25.0', + // }; + + // project.addons.push({ + // name: 'ember-cli-babel', + // parent: project, + // pkg: { + // version: '7.26.5', + // }, + // addons: [], + // }); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, true); + // assert.strictEqual(result.hasActionableSuggestions, true); + // assert.ok( + // result.globalMessage.includes( + // '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + // ) + // ); + // }); + // QUnit.test( + // 'projects has no devDependencies, but old ember-cli-babel found in addons array', + // function (assert) { + // project.pkg.devDependencies = {}; + + // project.addons.push({ + // name: 'ember-cli-babel', + // parent: project, + // pkg: { + // version: '7.26.5', + // }, + // addons: [], + // }); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, true); + // assert.strictEqual(result.hasActionableSuggestions, true); + // assert.ok( + // result.globalMessage.includes( + // '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + // ) + // ); + // } + // ); + + // QUnit.test('projects uses linked ember-cli-babel', function (assert) { + // project.pkg.devDependencies = { + // 'ember-cli-babel': 'link:./some/path/here', + // }; + + // let otherAddon = { + // name: 'other-thing-here', + // parent: project, + // pkg: {}, + // addons: [], + // }; + + // otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); + // project.addons.push(buildBabel(project, '7.26.6'), otherAddon); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, true); + // assert.strictEqual(result.hasActionableSuggestions, true); + + // assert.ok( + // result.globalMessage.includes( + // '* If using yarn, run `npx yarn-deduplicate --packages ember-cli-babel`' + // ) + // ); + // assert.ok(result.globalMessage.includes('* If using npm, run `npm dedupe`')); + // }); + + // QUnit.test('projects own ember-cli-babel is up to date', function (assert) { + // project.pkg.devDependencies = { + // 'ember-cli-babel': '^7.26.0', + // }; + + // project.addons.push({ + // name: 'ember-cli-babel', + // parent: project, + // pkg: { + // version: '7.26.6', + // }, + // addons: [], + // }); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, false); + // assert.strictEqual(result.hasActionableSuggestions, false); + // assert.notOk( + // result.globalMessage.includes( + // '* Upgrade your `devDependencies` on `ember-cli-babel` to `^7.26.6`' + // ) + // ); + // }); + + // QUnit.test('transient babel that is out of date', function (assert) { + // project.pkg.devDependencies = { + // 'ember-cli-babel': '^7.26.0', + // }; + + // let otherAddon = { + // name: 'other-thing-here', + // parent: project, + // pkg: { + // dependencies: { + // 'ember-cli-babel': '^7.25.0', + // }, + // }, + // addons: [], + // }; + + // otherAddon.addons.push(buildBabel(otherAddon, '7.26.5')); + // project.addons.push(buildBabel(project, '7.26.6'), otherAddon); + + // let result = globalDeprecationInfo(project, env); + // assert.strictEqual(result.shouldIssueSingleDeprecation, true); + // assert.strictEqual(result.hasActionableSuggestions, true); + // assert.ok(result.globalMessage.includes('* other-thing-here@7.26.5 (Compatible)')); + // }); +});