diff --git a/.gitignore b/.gitignore index 95433dc..6ec9577 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /libpeerconnection.log npm-debug.log testem.log +/.vscode diff --git a/AUTHORS.txt b/AUTHORS.txt index 3dfab5c..62968b3 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -1,15 +1,23 @@ +Burnashov Evgeny Chad Hietala Chris Thoburn David J. Hamilton +Edilberto Ruvalcaba Edilberto Ruvalcaba Ilya Radchenko Justin Lan Katie Gengler Kyle Turney Marc Lynch +Marc Lynch Mikael Riska +Nathaniel Furniss +Nathaniel Furniss Quinn C. Hoyer +Robert Jackson +Robert Jackson Ryunosuke Sato +Sang Mercado Sean Johnson Sean Johnson Stefan Penner diff --git a/CHANGELOG.md b/CHANGELOG.md index 17c9a6c..6d22b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +1.1.1 / 2017-11-13 +================== + + * Fix issues when running under eslint@4 (fixes `Cannot read property 'getFilename' of undefined`). + +1.1.0 / 2017-10-03 +================== + + * Fixes issues with imports (#91) + * Fixes require-ember-lifeline rule to scope it to only ember file types (#93) + * Merge pull request #89 from nlfurniss/dry-parserOptions + +1.0.0 / 2017-09-13 +================== + + * Fix `no-anonymous-once` not recognizing context function + * Fix `no-global-jquery` returning false positive for var assignment #85 + * Fix logic to detect jQuery being assigned to a var + * Create `no-jquery-selector` rule + +0.9.0 / 2017-08-31 +================== + + * Fixing rule `no-jquery-methods` to remove reference to `Ember` + * Add tests for rule `no-jquery-methods` + * Update docs and rule `no-jquery-methods` to include checks for CallExpressions + * Adding new rule: `no-anonymous-once` + +0.8.0 / 2017-08-15 +================== + + * Add test for rule `no-side-effect-cp` + * Fix `imports` util + * Update guide for `no-jquery-method` + * Update default BLACKLIST from config `no-jquery` + * Fix RELEASE.md 0.7.1 / 2017-06-27 ================== diff --git a/config/no-jquery.js b/config/no-jquery.js index 6a9d345..75c5292 100644 --- a/config/no-jquery.js +++ b/config/no-jquery.js @@ -1,88 +1,3 @@ -const BLACKLIST = [ - 'add', - 'addBack', - 'after', - 'ajaxComplete', - 'ajaxError', - 'ajaxSend', - 'ajaxStart', - 'ajaxStop', - 'ajaxSuccess', - 'andSelf', - 'before', - 'bind', - 'change', - 'clearQueue', - 'clone', - 'contents', - 'contextmenu', - 'dblclick', - 'delay', - 'delegate', - 'dequeue', - 'detach', - 'end', - 'error', - 'fadeIn', - 'fadeTo', - 'fadeToggle', - 'finish', - 'focusin', - 'focusout', - 'hover', - 'insertAfter', - 'insertBefore', - 'keydown', - 'keypress', - 'keyup', - 'last', - 'load', - 'mousedown', - 'mouseenter', - 'mouseleave', - 'mousemove', - 'mouseout', - 'mouseover', - 'mouseup', - 'next', - 'nextAll', - 'nextUntil', - 'not', - 'parentsUntil', - 'prepend', - 'prependTo', - 'prev', - 'revUntil', - 'promise', - 'queue', - 'removeData', - 'removeProp', - 'replaceAll', - 'replaceWith', - 'resize', - 'scroll', - 'scrollLeft', - 'select', - 'serialize', - 'show', - 'size', - 'slice', - 'slideDown', - 'slideToggle', - 'stop', - 'submit', - 'toArray', - 'toggle', - 'toggleClass', - 'unbind', - 'undelegate', - 'unload', - 'unwrap', - 'wrap', - 'wrapAll', - 'wrapInner' -]; - module.exports = { parserOptions: { ecmaVersion: 6, @@ -97,6 +12,6 @@ module.exports = { extends: require.resolve('./recommended.js'), rules: { // Custom rules - 'ember-best-practices/no-jquery-methods': [2, [BLACKLIST]] + 'ember-best-practices/no-jquery-methods': [2, []] } }; diff --git a/config/recommended.js b/config/recommended.js index 16e34e5..842aec3 100644 --- a/config/recommended.js +++ b/config/recommended.js @@ -12,6 +12,7 @@ module.exports = { rules: { // Custom rules 'ember-best-practices/no-side-effect-cp': 2, + 'ember-best-practices/no-anonymous-once': 2, 'ember-best-practices/no-attrs': 2, 'ember-best-practices/no-observers': 2, 'ember-best-practices/require-dependent-keys': 2, diff --git a/guides/rules/no-jquery-methods.md b/guides/rules/no-jquery-methods.md index 640a5d7..aa50aa3 100644 --- a/guides/rules/no-jquery-methods.md +++ b/guides/rules/no-jquery-methods.md @@ -2,5 +2,28 @@ **TL;DR** The intent of this rule is to identify any blacklisted jQuery methods as deprecated. -Provides the ability to blacklist a set of jQuery methods for deprecation. +Provides the ability to blacklist a set of jQuery methods for deprecation. The eventual goal being a complete removal of jQuery. + +# Usage + +Pass an argument array containing the specific blacklisted jQuery methods your consuming application is flagging for. + +```js +const BLACKLIST = [ + 'add', + 'addBack', + 'after', + 'ajaxComplete' +]; + +rules: { + 'ember-best-practices/no-jquery-methods': ['error', BLACKLIST] +} +``` +To help generate this blacklist array, you can run this snippet in the console of your app: +```js +// In the dev tools console of your app. +let $methods = [...new Set(Object.keys($).concat(Object.keys($.fn)))]; +copy(`'${$methods.join('\',\r\n\'')}'`) +``` diff --git a/guides/rules/no-jquery-selector.md b/guides/rules/no-jquery-selector.md new file mode 100644 index 0000000..01491ae --- /dev/null +++ b/guides/rules/no-jquery-selector.md @@ -0,0 +1,29 @@ +# No jQuery Selector + +**TL;DR** The intent of this rule is to identify using jQuery to select DOM elements as deprecated. + +# Examples + +```js +// Deprecated +export default Ember.Component({ + getElement(selector) { + return this.$(selector); + }, + + click() { + this.$().focus(); + } +}); + +// Vanilla JS +export default Ember.Component({ + getElement(selector) { + return this.element.querySelector(selector); + }, + + click() { + this.element.focus(); + } +}); +``` diff --git a/lib/rules/no-2.0.0-hooks.js b/lib/rules/no-2.0.0-hooks.js index 6142903..c890aa2 100644 --- a/lib/rules/no-2.0.0-hooks.js +++ b/lib/rules/no-2.0.0-hooks.js @@ -3,6 +3,8 @@ */ 'use strict'; +const { get } = require('../utils/get'); + // TODO // Write docs for this once we feel it should be recommended. const MESSAGE = 'Do not use the 2.0.0 hooks as they are not conducive to the programming model.'; @@ -21,8 +23,8 @@ module.exports = { return { ObjectExpression(node) { node.properties.forEach((property) => { - let name = property.key.name; - if (MISTAKE_HOOKS.indexOf(name) > -1) { + let name = get(property, 'key.name'); + if (name && MISTAKE_HOOKS.indexOf(name) > -1) { context.report(property, MESSAGE); } }); diff --git a/lib/rules/no-anonymous-once.js b/lib/rules/no-anonymous-once.js new file mode 100644 index 0000000..8e49c38 --- /dev/null +++ b/lib/rules/no-anonymous-once.js @@ -0,0 +1,132 @@ +/** + * @fileOverview Disallow the use of anonymous functions passed to scheduleOnce and once. + */ +'use strict'; + +const { getCaller, cleanCaller, isCallingWithApply, isCallingWithCall } = require('../utils/caller'); +const { collectObjectPatternBindings } = require('../utils/destructed-binding'); +const { getEmberImportBinding } = require('../utils/imports'); +const { get } = require('../utils/get'); +const MESSAGE + = `The uniqueness once offers is based on function uniqueness, each invocation of this line will always create a new + function instance, resulting in uniqueness checks, that will never be hit. Please replace with a named function. + Reference: https://emberjs.com/api/ember/2.14/namespaces/Ember.run/methods/scheduleOnce?anchor=scheduleOnce`; + +const DISALLOWED_OBJECTS = ['Ember.run', 'run']; +const SCHEDULE_ONCE = 'scheduleOnce'; +const RUN_METHODS = [SCHEDULE_ONCE, 'once']; + +/** + * Extracts the method that we are trying to run once from the list of arguments. + * An optional target parameter can be passed in as the first parameter so we need + * to check the length of the array to determine where our function is being passed in. + */ +function getMethodToRunOnce(args) { + return args.length > 1 ? args[1] : args[0]; +} + +/** + * scheduleOnce takes in the queue name as the first parameter. In this function we will remove + * that parameter from the array to make it look the same as the arguments to once and facilitate + * extracting the method that we are trying to run once out of the args. + */ +function normalizeArguments(caller, args) { + let mut = args.slice(); + + if (isCallingWithCall(caller)) { + // Whenever the action was called .call we want to remove the context parameter + mut.shift(); + } else if (isCallingWithApply(caller)) { + // Whenever the action was called with .apply we want to get the arguments with which the function + // would actually get called + mut = mut[1].elements.slice(); + } + + // scheduleOnce takes in the queue name as the first parameter so we have to remove it have a similar + // structure as "once" + if (cleanCaller(caller).indexOf(SCHEDULE_ONCE) > -1) { + mut.shift(); + } + + return mut; +} +/** + * Determines whether a function is anonymous based on whether it was a name + * or if it is a method on the current context. + * @param {ASTNode} fn + * @return {Boolean} + */ +function isAnonymousFunction(fn) { + return !(get(fn, 'name') || get(fn, 'object.type') === 'ThisExpression'); +} + +function isString(node) { + return node.type === 'Literal' && typeof node.value === 'string'; +} + +function mergeDisallowedCalls(objects) { + return objects + .reduce((calls, obj) => { + RUN_METHODS.forEach((method) => { + calls.push(`${obj}.${method}`); + }); + + return calls; + }, []); +} + +module.exports = { + docs: { + description: 'Disallow use of anonymous functions when use in scheduleOnce or once', + category: 'Best Practices', + recommended: true + }, + meta: { + message: MESSAGE + }, + create(context) { + let emberImportBinding; + let disallowedCalls = mergeDisallowedCalls(DISALLOWED_OBJECTS); + + return { + ImportDefaultSpecifier(node) { + emberImportBinding = getEmberImportBinding(node); + }, + + ObjectPattern(node) { + if (!emberImportBinding) { + return; + } + + /** + * Retrieves the deconstructed bindings from the Ember import, accounting for aliasing + * of the import. + */ + disallowedCalls = disallowedCalls.concat( + mergeDisallowedCalls( + collectObjectPatternBindings(node, { + [emberImportBinding]: ['run'] + }) + ) + ); + }, + + CallExpression(node) { + const caller = getCaller(node); + + if (!disallowedCalls.includes(cleanCaller(caller))) { + return; + } + + const normalizedArguments = normalizeArguments(caller, node.arguments); + const fnToRunOnce = getMethodToRunOnce(normalizedArguments); + + // The fnToRunceOnce is a string it means that it will be resolved on the target at the time once or + // scheduleOnce is invoked. + if (isAnonymousFunction(fnToRunOnce) && !isString(fnToRunOnce)) { + context.report(node, MESSAGE); + } + } + }; + } +}; diff --git a/lib/rules/no-global-jquery.js b/lib/rules/no-global-jquery.js index 7a23324..18ff282 100644 --- a/lib/rules/no-global-jquery.js +++ b/lib/rules/no-global-jquery.js @@ -2,12 +2,15 @@ * @fileOverview Disallow the use of global `$`. */ 'use strict'; - +const { get } = require('../utils/get'); const { getEmberImportBinding } = require('../utils/imports'); +const { + isVariableAssigmentGlobalJquery, + JQUERY_ALIASES +} = require('../utils/jquery'); const { collectObjectPatternBindings } = require('../utils/destructed-binding'); const MESSAGE = 'Do not use global `$` or `jQuery`.'; -const ALIASES = ['$', 'jQuery']; /** * Determines if this expression matches a global jQuery invocation, either `$` or `jQuery`. @@ -15,7 +18,7 @@ const ALIASES = ['$', 'jQuery']; * @returns {Boolean} Returns true if the expression matches, otherwise false. */ function isGlobalJquery(node) { - return node.callee && ALIASES.includes(node.callee.name); + return node.callee && JQUERY_ALIASES.includes(node.callee.name); } module.exports = { @@ -30,6 +33,7 @@ module.exports = { create(context) { let emberImportBinding; let destructuredAssignment = null; + let emberJqueryVariables = []; function isDestructured(node) { return node && node.callee && node.callee.name === destructuredAssignment; @@ -48,8 +52,18 @@ module.exports = { } }, + VariableDeclarator(node) { + if (isVariableAssigmentGlobalJquery(node)) { + context.report(node, MESSAGE); + } else { + const varName = get(node, 'id.name'); + emberJqueryVariables.push(varName); + } + }, + CallExpression(node) { - if (!isDestructured(node) && isGlobalJquery(node)) { + const calleeName = get(node, 'callee.name'); + if (!isDestructured(node) && isGlobalJquery(node) && !emberJqueryVariables.includes(calleeName)) { context.report(node, MESSAGE); } } diff --git a/lib/rules/no-jquery-methods.js b/lib/rules/no-jquery-methods.js index 0d25f9a..322020b 100644 --- a/lib/rules/no-jquery-methods.js +++ b/lib/rules/no-jquery-methods.js @@ -3,19 +3,11 @@ */ 'use strict'; -const { get } = require('../utils/get'); +const { get, getParent } = require('../utils/get'); +const { isJQueryCallee, isVariableAssigmentJquery } = require('../utils/jquery'); function getMessage(blackListName) { - return `The use of ${blackListName} method has been deprecated.`; -} - -function isJQueryCaller(node, name = null) { - let oName = get(node, 'object.callee.object.name') === 'Ember'; - let pName = get(node, 'object.callee.property.name') === '$'; - let cName = get(node, 'object.callee.name') === '$'; - let lName = get(node, 'object.name') === name; - - return oName || pName || cName || lName; + return `The use of jQuery's ${blackListName} method has been deprecated.`; } function getLocalImportName(node, sourceName) { @@ -36,33 +28,56 @@ module.exports = { }, create(context) { const BLACKLIST = context.options[0] || []; - let jQueryLocalName = null; - let varDecIDName = null; + let funcScopeJqueryVariables = []; + let moduleScopeJqueryVariables = []; return { ImportDeclaration(node) { let localName = getLocalImportName(node, 'jquery'); if (localName.length > 0) { - jQueryLocalName = localName[0]; + moduleScopeJqueryVariables.push(localName[0]); } }, VariableDeclarator(node) { - varDecIDName = get(node, 'id.name'); - jQueryLocalName = jQueryLocalName === get(node, 'init.name') ? varDecIDName : null; + if (isVariableAssigmentJquery(node)) { + const varName = get(node, 'id.name'); + const isFuncScope = !!getParent(node, (parent) => parent.type === 'FunctionDeclaration'); // Determine if variable is in function scope or global scope. + + if (isFuncScope) { + funcScopeJqueryVariables.push(varName); + } else { + moduleScopeJqueryVariables.push(varName); + } + } + }, + + CallExpression(node) { + const calleeName = get(node, 'callee.property.name'); + const parentCalleeName = get(node, 'callee.object.property.name'); + if (BLACKLIST.includes(calleeName) && (parentCalleeName === '$' || isJQueryCallee(node, moduleScopeJqueryVariables, funcScopeJqueryVariables))) { + context.report({ node: node.callee.property, message: getMessage(`${parentCalleeName}.${calleeName}()`) }); + } }, MemberExpression(node) { let propertyName = get(node, 'property.name'); + // If the node doesn't have a name, return early. + if (!propertyName) { + return; + } let blackListName = BLACKLIST.includes(propertyName) ? propertyName : false; let isThisExpression = get(node, 'object.type').includes('ThisExpression'); - jQueryLocalName = isThisExpression && propertyName.includes('$') ? varDecIDName : jQueryLocalName; - - if (!isThisExpression && blackListName && isJQueryCaller(node, jQueryLocalName)) { + if (!isThisExpression && blackListName && isJQueryCallee(node, moduleScopeJqueryVariables, funcScopeJqueryVariables)) { context.report({ node: node.property, message: getMessage(blackListName) }); } + }, + + FunctionDeclaration() { + // Reset the function scope variables for each function declaration. + funcScopeJqueryVariables.length = 0; } }; } diff --git a/lib/rules/no-jquery-selector.js b/lib/rules/no-jquery-selector.js new file mode 100644 index 0000000..a2c343e --- /dev/null +++ b/lib/rules/no-jquery-selector.js @@ -0,0 +1,43 @@ +/** + * @fileOverview Disallow the use of jQuery selector. + */ +'use strict'; + +const { get } = require('../utils/get'); +const componentElementMessage = 'The use of this.$() to get a component\'s element is deprecated. Use \'this.element\' instead'; + +function getSelectorMessage(selector) { + return `The use of $(${selector}) to select an element is deprecated.`; +} + +module.exports = { + docs: { + category: 'Best Practices', + recommended: true + }, + meta: { + componentElementMessage, + getSelectorMessage + }, + create(context) { + return { + CallExpression(node) { + const calleeName = get(node, 'callee.property.name'); + const problemNode = node.callee; + const firstNodeArg = node.arguments && node.arguments[0]; + + if (calleeName === '$') { + let message; + if (firstNodeArg) { + // Get the arg's value if it's a string or name if it's a variable. + const argName = firstNodeArg.value || firstNodeArg.name; + message = getSelectorMessage(argName); + } else { + message = componentElementMessage; + } + context.report({ node: problemNode, message }); + } + } + }; + } +}; diff --git a/lib/rules/no-side-effect-cp.js b/lib/rules/no-side-effect-cp.js index b32c63c..75f0d41 100644 --- a/lib/rules/no-side-effect-cp.js +++ b/lib/rules/no-side-effect-cp.js @@ -2,12 +2,28 @@ * @fileOverview Disallow use of computed properties that include side-effect producing calls. */ +const { get } = require('../utils/get'); const { getCaller, cleanCaller } = require('../utils/caller'); const isCPGetter = require('../utils/computed-property'); const { getEmberImportBinding, collectImportBindings } = require('../utils/imports'); const { collectObjectPatternBindings } = require('../utils/destructed-binding'); -let SIDE_EFFECTS = ['this.send', 'this.sendAction', 'this.sendEvent', 'Em.sendEvent', 'Ember.sendEvent', 'this.trigger', 'this.set', 'Ember.set', 'Em.set', 'this.setProperties', 'Ember.setProperties', 'Em.setProperties']; +function sideEffectsForEmber(importName) { + return [ + `${importName}.sendEvent`, + `${importName}.set`, + `${importName}.setProperties` + ]; +} + +const SIDE_EFFECTS = Object.freeze([].concat( + 'this.send', + 'this.sendAction', + 'this.trigger', + sideEffectsForEmber('this'), + sideEffectsForEmber('Em'), + sideEffectsForEmber('Ember') +)); const MESSAGE = 'Do not send events or actions in Computed Properties. This will cause data flow issues in the application, where the accessing of a property causes some side-effect. You should only send actions on behalf of user initiated events. Please see the following guide for more information: https://github.com/ember-best-practices/eslint-plugin-ember-best-practices/blob/master/guides/rules/no-side-effect-cp.md'; @@ -21,45 +37,54 @@ module.exports = { message: MESSAGE }, create(context) { - let inCPGettter = false; - let bindings = []; - let emberImportBinding; + let inCPGetter = false; + let importedSideEffectBindings = []; + let emberImportBinding, importedCPBinding; + let sideEffects = SIDE_EFFECTS.slice(); return { ImportDeclaration(node) { - bindings = collectImportBindings(node, { + importedSideEffectBindings = importedSideEffectBindings.concat(collectImportBindings(node, { '@ember/object': ['set', 'setProperties'], '@ember/object/events': ['sendEvent'] - }); + })); }, ObjectPattern(node) { if (emberImportBinding) { - SIDE_EFFECTS = SIDE_EFFECTS.concat(collectObjectPatternBindings(node, { + sideEffects = sideEffects.concat(collectObjectPatternBindings(node, { [emberImportBinding]: ['set', 'setProperties', 'sendEvent'] })); } }, ImportDefaultSpecifier(node) { - emberImportBinding = getEmberImportBinding(node); + let localEmberImportBinding = getEmberImportBinding(node); + if (localEmberImportBinding) { + emberImportBinding = localEmberImportBinding; + sideEffects = sideEffects.concat(sideEffectsForEmber(emberImportBinding)); + } + + if (get(node, 'parent.source.value') === '@ember/computed') { + importedCPBinding = node.local.name; + } }, FunctionExpression(node) { - if (isCPGetter(node)) { - inCPGettter = true; + if (isCPGetter(node, emberImportBinding, importedCPBinding)) { + inCPGetter = node; } }, 'FunctionExpression:exit'(node) { - if (isCPGetter(node)) { - inCPGettter = false; + if (node === inCPGetter) { + inCPGetter = null; } }, CallExpression(node) { - if (inCPGettter) { + if (inCPGetter) { let caller = cleanCaller(getCaller(node)); - let hasSideEffect = SIDE_EFFECTS.includes(caller) || bindings.includes(caller); + let hasSideEffect = sideEffects.includes(caller) || importedSideEffectBindings.includes(caller); if (hasSideEffect) { context.report(node, MESSAGE); } diff --git a/lib/rules/require-ember-lifeline.js b/lib/rules/require-ember-lifeline.js index 4a743b1..c22129d 100644 --- a/lib/rules/require-ember-lifeline.js +++ b/lib/rules/require-ember-lifeline.js @@ -6,6 +6,7 @@ const { getCaller, cleanCaller } = require('../utils/caller'); const { getEmberImportBinding } = require('../utils/imports'); const { collectObjectPatternBindings } = require('../utils/destructed-binding'); +const LINTABLE_FILE_PATTERNS = /\/components|routes|controllers|services\//i; let DISALLOWED_OBJECTS = ['Ember.run', 'run']; let RUN_METHODS = ['later', 'next', 'debounce', 'throttle']; const LIFELINE_METHODS = ['runTask', 'runTask', 'debounceTask', 'throttleTask']; @@ -28,6 +29,10 @@ function mergeDisallowedCalls(objects) { }, []); } +function isLintableFile(context) { + return LINTABLE_FILE_PATTERNS.test(context.getFilename()); +} + module.exports = { docs: { description: 'Please use the lifecycle-aware tasks from ember-lifeline instead of Ember.run.*.', @@ -61,6 +66,10 @@ module.exports = { MemberExpression(node) { let caller = cleanCaller(getCaller(node)); + if (!isLintableFile(context)) { + return; + } + if (disallowedCalls.includes(caller)) { context.report(node, getMessage(caller)); } diff --git a/lib/utils/caller.js b/lib/utils/caller.js index d5fa008..6ec0234 100644 --- a/lib/utils/caller.js +++ b/lib/utils/caller.js @@ -40,7 +40,17 @@ function cleanCaller(caller) { return caller; } +function isCallingWithCall(caller) { + return endsWith(caller, '.call'); +} + +function isCallingWithApply(caller) { + return endsWith(caller, '.apply'); +} + module.exports = { getCaller, - cleanCaller + cleanCaller, + isCallingWithApply, + isCallingWithCall }; diff --git a/lib/utils/computed-property.js b/lib/utils/computed-property.js index 3c83fbb..a3951c8 100644 --- a/lib/utils/computed-property.js +++ b/lib/utils/computed-property.js @@ -1,12 +1,12 @@ const { get } = require('./get'); const { getCaller } = require('./caller'); -function isCPDesc(node, method) { +function isCPDesc(node, importedEmberName, importedCPName) { if (node.type !== 'FunctionExpression') { return false; } - if (get(node, 'parent.key.name') !== method) { + if (get(node, 'parent.key.name') !== 'get') { return false; } @@ -22,12 +22,12 @@ function isCPDesc(node, method) { return false; } let callee = greatGrandParent.callee; - if (greatGrandParent.type === 'Identifier' && callee.name === 'computed') { + if (greatGrandParent.type === 'Identifier' && (callee.name === 'computed' || callee.name === importedCPName)) { return true; } if (callee.type === 'MemberExpression') { let caller = getCaller(callee); - return caller === 'Ember.computed' || caller === 'Em.computed'; + return caller === 'Ember.computed' || caller === 'Em.computed' || caller === `${importedEmberName}.computed`; } return false; // don't know how you could get here } @@ -40,7 +40,7 @@ function isPrototypeExtCP(node) { return get(node, 'parent.property.name'); } -function isCPAccessor(node) { +function isCPAccessor(node, importedEmberName, importedCPName) { if (node.type !== 'FunctionExpression') { return false; } @@ -51,18 +51,20 @@ function isCPAccessor(node) { } let callee = parent.callee; - if (callee.type === 'Identifier' && callee.name === 'computed') { + if (callee.type === 'Identifier' && (callee.name === 'computed' || callee.name === importedCPName)) { return true; } if (callee.type === 'MemberExpression') { let caller = getCaller(callee); - return caller === 'Ember.computed' || caller === 'Em.computed'; + return caller === 'Ember.computed' || caller === 'Em.computed' || caller === `${importedEmberName}.computed`; } return false; } -module.exports = function isCPGetter(node) { - return isCPDesc(node, 'get') || isCPAccessor(node) || isPrototypeExtCP(node); +module.exports = function isCPGetter(node, importedEmberName, importedCPName) { + return isCPDesc(node, importedEmberName, importedCPName) + || isCPAccessor(node, importedEmberName, importedCPName) + || isPrototypeExtCP(node); }; diff --git a/lib/utils/imports.js b/lib/utils/imports.js index 82243f0..b4b021a 100644 --- a/lib/utils/imports.js +++ b/lib/utils/imports.js @@ -7,7 +7,8 @@ function collectImportBindings(node, imports) { if (sourceName) { return node.specifiers.filter((specifier) => { - return importedBindings.includes(specifier.imported.name); + let name = get(specifier, 'imported.name'); + return 'ImportSpecifier' === specifier.type && name && importedBindings.includes(name); }).map((specifier) => specifier.local.name); } diff --git a/lib/utils/jquery.js b/lib/utils/jquery.js new file mode 100644 index 0000000..7372a74 --- /dev/null +++ b/lib/utils/jquery.js @@ -0,0 +1,82 @@ +const { get } = require('../utils/get'); + +const JQUERY_ALIASES = ['$', 'jQuery']; + +/** + * Determines whether a given node's callee is jQuery. + * @param {ASTNode} node + * @param {...Array} localJqueryVars Array(s) of local variables that have jQuery assigned to them. + * @return {Boolean} + */ +function isJQueryCallee(node, ...localJqueryVars) { + const allJqueryAliases = localJqueryVars.reduce((accumulator, newArray) => { + return accumulator.concat(newArray); + }, JQUERY_ALIASES); + + const calleePropertyNameisJquery = allJqueryAliases.includes(get(node, 'object.callee.property.name')); + const calleeNameIsJquery = allJqueryAliases.includes(get(node, 'object.callee.name')); + const objectNameisJquery = allJqueryAliases.includes(get(node, 'object.name')); + + return calleePropertyNameisJquery + || calleeNameIsJquery + || objectNameisJquery; +} + +/** + * Helper function that returns the name of what was assigned to the variable. + * @param {ASTNode} node + * @return {String} + */ +function getVarAssignmentName(node) { + // Get the name of what was assigned to the variable. + return get(node, 'init.callee.property.name') || get(node, 'init.name') || get(node, 'init.property.name'); +} + +/** + * Determines whether a variable assignment's callee is Ember/this. + * @param {ASTNode} node + * @return {Boolean} + */ +function isVarAssignmentCalleeEmber(node) { + // A variable's callee object's name tells us whether this is a usage of Ember.$ or global $. + const isVarCalleeObjNameEmber = get(node, 'init.callee.object.name') === 'Ember' || get(node, 'init.object.name') === 'Ember'; + // Whether or not the callee oject is `this.$`. + const isVarCalleeObjThis = get(node, 'init.callee.object.type') === 'ThisExpression'; + + return isVarCalleeObjNameEmber || isVarCalleeObjThis; +} + +/** + * Determines whether a variable's assignment is global jQuery. + * @param {ASTNode} node + * @return {Boolean} + */ +function isVariableAssigmentGlobalJquery(node) { + return isVariableAssigmentJquery(node) && !isVarAssignmentCalleeEmber(node); +} + +/** + * Determines whether a variable's assignment is Ember jQuery. + * @param {ASTNode} node + * @return {Boolean} + */ +function isVariableAssigmentEmberJquery(node) { + return isVariableAssigmentJquery(node) && isVarAssignmentCalleeEmber(node); +} + +/** + * Determines whether a variable's assignment is Ember jQuery. + * @param {ASTNode} node + * @return {Boolean} + */ +function isVariableAssigmentJquery(node) { + return JQUERY_ALIASES.includes(getVarAssignmentName(node)); +} + +module.exports = { + isJQueryCallee, + isVariableAssigmentGlobalJquery, + isVariableAssigmentEmberJquery, + isVariableAssigmentJquery, + JQUERY_ALIASES +}; diff --git a/package.json b/package.json index a8103d8..23c15b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-ember-best-practices", - "version": "0.7.1", + "version": "1.1.1", "description": "Eslint rules for linting for anti-patterns in Ember applications.", "main": "lib/index.js", "directories": { @@ -29,7 +29,7 @@ "eslint": "^3.11.1", "mocha": "^3.2.0", "mocha-eslint": "^3.0.1", - "nyc": "^10.0.0" + "nyc": "^11.2.1" }, "dependencies": { "requireindex": "^1.1.0" diff --git a/tests/lib/rules/no-2.0.0-hooks.js b/tests/lib/rules/no-2.0.0-hooks.js index 8d97101..6e9d657 100644 --- a/tests/lib/rules/no-2.0.0-hooks.js +++ b/tests/lib/rules/no-2.0.0-hooks.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-2.0.0-hooks'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-2.0.0-hooks', rule, { valid: [ @@ -15,11 +20,7 @@ ruleTester.run('no-2.0.0-hooks', rule, { this.set('baz', true); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -111,10 +112,6 @@ ruleTester.run('no-2.0.0-hooks', rule, { this.nope = true; } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-anonymous-once.js b/tests/lib/rules/no-anonymous-once.js new file mode 100644 index 0000000..9fd2f0d --- /dev/null +++ b/tests/lib/rules/no-anonymous-once.js @@ -0,0 +1,175 @@ +const rule = require('../../../lib/rules/no-anonymous-once'); +const MESSAGE = rule.meta.message; +const RuleTester = require('eslint').RuleTester; +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); +const errors = [{ + message: MESSAGE +}]; + +ruleTester.run('no-anonymous-once', rule, { + valid: [ + { + code: ` + function noop() {} ; + export default Ember.Component({ + perform() { + Ember.run.scheduleOnce('afterRender', this, noop); + Ember.run.scheduleOnce('afterRender', noop); + run.scheduleOnce('afterRender', this, noop); + run.scheduleOnce('afterRender', noop); + run.scheduleOnce.apply(this, ['afterRender', this, noop]); + run.scheduleOnce.apply(this, ['afterRender', noop]); + run.scheduleOnce.call(this, 'afterRender', this, noop); + run.scheduleOnce.call(this, 'afterRender', noop); + } + });` + }, + { + code: ` + function noop() {} ; + export default Ember.Component({ + perform() { + Ember.run.once(this, noop); + Ember.run.once(noop); + run.once(this, noop); + run.once(noop); + run.once.apply(this, [this, noop]); + run.once.apply(this, [noop]); + run.once.call(this, this, noop); + run.once.call(this, noop); + } + });` + }, + { + code: ` + export default Ember.Component({ + cb: () => { + // do stuff + }, + + perform() { + Ember.run.once(this, 'cb'); + } + });` + }, + { + code: ` + export default Ember.Component({ + cb: () => { + // do stuff + }, + + perform() { + Ember.run.once(this, this.cb); + } + });` + }, + { + code: ` + function cb() { + //do stuff + } + export default Ember.Component({ + perform() { + Ember.run.once(this, cb); + } + });` + }, + { + code: ` + export default Ember.Component({ + cb: () => { + // do stuff + }, + + perform() { + Ember.run.scheduleOnce('afterRender', this, 'cb'); + } + });` + }, + { + code: ` + export default Ember.Component({ + cb: () => { + // do stuff + }, + + perform() { + Ember.run.scheduleOnce('afterRender', this, this.cb); + } + });` + }, + { + code: ` + function cb() { + //do stuff + } + export default Ember.Component({ + perform() { + Ember.run.scheduleOnce('afterRender', this, cb); + } + });` + } + ], + invalid: [ + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.scheduleOnce('afterRender', () => {}); + } + });`, + errors + }, + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.scheduleOnce('afterRender', this, () => {}); + } + });`, + errors + }, + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.once(this, () => {}); + } + });`, + errors + }, + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.once(() => {}); + } + });`, + errors + }, + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.once(function () {}); + } + });`, + errors + }, + { + code: ` + export default Ember.Component({ + perform() { + Ember.run.scheduleOnce('afterRender', function () {}); + } + });`, + errors + } + ] +}); diff --git a/tests/lib/rules/no-attrs-snapshot.js b/tests/lib/rules/no-attrs-snapshot.js index 9583c52..7312afa 100644 --- a/tests/lib/rules/no-attrs-snapshot.js +++ b/tests/lib/rules/no-attrs-snapshot.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-attrs-snapshot'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-attrs-snapshot', rule, { valid: [ @@ -21,11 +26,7 @@ ruleTester.run('no-attrs-snapshot', rule, { this.set('updated', false); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -43,11 +44,7 @@ ruleTester.run('no-attrs-snapshot', rule, { this.set('updated', false); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -67,10 +64,6 @@ ruleTester.run('no-attrs-snapshot', rule, { } } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -91,10 +84,6 @@ ruleTester.run('no-attrs-snapshot', rule, { } } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-attrs.js b/tests/lib/rules/no-attrs.js index 8d9fd89..f889787 100644 --- a/tests/lib/rules/no-attrs.js +++ b/tests/lib/rules/no-attrs.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-attrs'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-attrs', rule, { valid: [ @@ -12,11 +17,7 @@ ruleTester.run('no-attrs', rule, { this._super(...arguments); this.alias = this.concrete; } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -28,10 +29,6 @@ ruleTester.run('no-attrs', rule, { this.alias = this.attrs.concrete; } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -45,10 +42,6 @@ ruleTester.run('no-attrs', rule, { } } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-broken-super-chain.js b/tests/lib/rules/no-broken-super-chain.js index c2eee35..59d9cfe 100644 --- a/tests/lib/rules/no-broken-super-chain.js +++ b/tests/lib/rules/no-broken-super-chain.js @@ -2,7 +2,12 @@ const rule = require('../../../lib/rules/no-broken-super-chain'); const RuleTester = require('eslint').RuleTester; const { noSuper, tooManySupers } = rule.meta.messages; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-broken-super-chain', rule, { valid: [ @@ -16,11 +21,7 @@ ruleTester.run('no-broken-super-chain', rule, { somethingNotInit() { this.alias = this.concrete; } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -29,11 +30,7 @@ ruleTester.run('no-broken-super-chain', rule, { this._super(...arguments); this.get('foo'); } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -48,11 +45,7 @@ ruleTester.run('no-broken-super-chain', rule, { somethingNotInit() { this.alias = this.concrete; } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -60,11 +53,7 @@ ruleTester.run('no-broken-super-chain', rule, { init() { this.alias = this.concrete; } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -72,11 +61,7 @@ ruleTester.run('no-broken-super-chain', rule, { didInsertElement() { this.updateBlurHandler(true); } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -85,11 +70,7 @@ ruleTester.run('no-broken-super-chain', rule, { this._super(...arguments); this.updateBlurHandler(true); } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` }, { code: ` @@ -100,11 +81,7 @@ ruleTester.run('no-broken-super-chain', rule, { } }); - export default foo;`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + export default foo;` } ], invalid: [ @@ -115,10 +92,6 @@ ruleTester.run('no-broken-super-chain', rule, { this.alias = this.concrete; } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: noSuper }] @@ -130,10 +103,6 @@ ruleTester.run('no-broken-super-chain', rule, { this.get('foo'); } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: noSuper }] @@ -147,10 +116,6 @@ ruleTester.run('no-broken-super-chain', rule, { // this.alias = this.concrete; // } // });`, - // parserOptions: { - // ecmaVersion: 6, - // sourceType: 'module' - // } // }, // TODO // { @@ -161,10 +126,6 @@ ruleTester.run('no-broken-super-chain', rule, { // this._super(...arguments); // } // });`, - // parserOptions: { - // ecmaVersion: 6, - // sourceType: 'module' - // }, // errors: [{ // message: noThisBeforeSuper // }] @@ -178,10 +139,6 @@ ruleTester.run('no-broken-super-chain', rule, { this._super(...arguments); } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: tooManySupers }] @@ -194,10 +151,6 @@ ruleTester.run('no-broken-super-chain', rule, { // this.updateBlurHandler(true); // } // });`, - // parserOptions: { - // ecmaVersion: 6, - // sourceType: 'module' - // }, // errors: [{ // message: noSuper // }] diff --git a/tests/lib/rules/no-global-jquery.js b/tests/lib/rules/no-global-jquery.js index fbd3941..20c4716 100644 --- a/tests/lib/rules/no-global-jquery.js +++ b/tests/lib/rules/no-global-jquery.js @@ -1,22 +1,67 @@ const rule = require('../../../lib/rules/no-global-jquery'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); -const parserOptions = { - ecmaVersion: 6, - sourceType: 'module' -}; +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-global-jquery', rule, { valid: [ + { + code: ` + export default Component.extend({ + focusWithJQuery() { + const $ = Ember.$; + $('.class').focus(); + } + })` + }, + { + code: ` + export default Component.extend({ + focusWithJQuery() { + const Jquery = Ember.$; + Jquery('.class').focus(); + } + })` + }, + { + code: ` + export default Component.extend({ + focusWithJQuery() { + const find = Ember.$; + find('.class').focus(); + } + })` + }, + { + code: ` + const $ = Ember.$; + export default Component.extend({ + focusWithJQuery() { + $('.class').focus(); + } + })` + }, + { + code: ` + const jQuery = Ember.$; + export default Component.extend({ + focusWithJQuery() { + jQuery('.class').focus(); + } + })` + }, { code: ` export default Ember.Component({ valid1() { this.v1 = Ember.$('.v1'); }, - });`, - parserOptions + });` }, { code: ` @@ -24,8 +69,7 @@ ruleTester.run('no-global-jquery', rule, { valid2() { this.v2 = this.$(); }, - });`, - parserOptions + });` }, { code: ` @@ -35,8 +79,7 @@ ruleTester.run('no-global-jquery', rule, { this.v3 = Ember.$('v3'); } } - });`, - parserOptions + });` }, { code: ` @@ -46,8 +89,27 @@ ruleTester.run('no-global-jquery', rule, { this.v4 = this.$('v4'); } } - });`, - parserOptions + });` + }, + { + code: ` + export default Ember.Component({ + actions: { + valid4() { + const elem = this.$(); + } + } + });` + }, + { + code: ` + export default Ember.Component({ + actions: { + valid4() { + const classElem = this.$('.class'); + } + } + });` }, { code: ` @@ -59,11 +121,7 @@ ruleTester.run('no-global-jquery', rule, { init() { this.el = $('.test'); } - });`, - parserOptions, - errors: [{ - message: MESSAGE - }] + });` }, { code: ` @@ -77,11 +135,7 @@ ruleTester.run('no-global-jquery', rule, { this.inv1 = $('.invalid1'); } } - });`, - parserOptions, - errors: [{ - message: MESSAGE - }] + });` }, { code: ` @@ -93,11 +147,7 @@ ruleTester.run('no-global-jquery', rule, { init() { this.el = foo('.test'); } - });`, - parserOptions, - errors: [{ - message: MESSAGE - }] + });` }, { code: ` @@ -111,11 +161,7 @@ ruleTester.run('no-global-jquery', rule, { this.inv1 = foo('.invalid1'); } } - });`, - parserOptions, - errors: [{ - message: MESSAGE - }] + });` } ], invalid: [ @@ -126,7 +172,6 @@ ruleTester.run('no-global-jquery', rule, { this.el = $('.test'); } });`, - parserOptions, errors: [{ message: MESSAGE }] @@ -140,7 +185,6 @@ ruleTester.run('no-global-jquery', rule, { } } });`, - parserOptions, errors: [{ message: MESSAGE }] @@ -152,7 +196,6 @@ ruleTester.run('no-global-jquery', rule, { this.el = jQuery('.test'); } });`, - parserOptions, errors: [{ message: MESSAGE }] @@ -166,7 +209,166 @@ ruleTester.run('no-global-jquery', rule, { } } });`, - parserOptions, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const jayQuery = $; + export default Ember.Component({ + badFun() { + jayQuery('.class'); + } + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const jayQuery = $; + export default Ember.Component({ + badFun() { + jayQuery.ajax(); + } + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const jayQuery = jQuery; + export default Ember.Component({ + badFun() { + jayQuery('.class'); + } + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const jayQuery = jQuery; + export default Ember.Component({ + badFun() { + jayQuery.ajax(); + } + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + export default Ember.Component({ + func() { + let a = jQuery; + let b = 'notDollarSign'; + + a('.find-me').click(); + } + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + export default Component.extend({ + doStuffWithJQuery() { + let find = jQuery; + find('.class').focus(); + }, + + nonJQueryStuff() { + let find = document.querySelector; + find('.class').focus(); + } + })`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + export default Component.extend({ + nonJQueryStuff() { + let find = document.querySelector; + find('.class').focus(); + + }, + + doStuffWithJQuery() { + let find = jQuery; + find('.class').focus(); + } + })`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + let find = Ember.$; + export default Component.extend({ + nonJQueryStuff() { + let find = document.querySelector; + find('.class').focus(); + + }, + + doStuffWithJQuery() { + let find = jQuery; + find('.class').focus(); + } + })`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + let find = $; + export default Component.extend({ + nonJQueryStuff() { + let find = document.querySelector; + find('.class').focus(); + + }, + + doStuffWithJQuery() { + find('.class').focus(); + } + })`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const $ = jQuery; + export default Component.extend({ + focusWithJQuery() { + let $ = Ember.$; + $('.class').focus(); + } + })`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + const $ = Ember.$; + export default Component.extend({ + focusWithJQuery() { + let $ = $; + $('.class').focus(); + } + })`, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-jquery-methods.js b/tests/lib/rules/no-jquery-methods.js index 5defcbd..86f9604 100644 --- a/tests/lib/rules/no-jquery-methods.js +++ b/tests/lib/rules/no-jquery-methods.js @@ -34,10 +34,74 @@ ruleTester.run('no-jquery-methods', rule, { export default Ember.Component({ init() { const myObj = {}; - myObj[${BLACKLISTMETHOD}](); + myObj[${BLACKLISTMETHOD}](); } });`, options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$.notBlacklistedMethod(); + } + });`, + options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + init() { + Ember.$.notBlacklistedMethod(); + } + });`, + options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + init() { + Ember.get().add(); + } + });`, + options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$().add(); + } + });`, + options: [] + }, + { + code: ` + export default Ember.Component({ + init() { + const myVar = this[this.myProp]; + } + });`, + options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + init() { + const q = this[\`\${myVar}\`]; + } + });`, + options: [BLACKLISTMETHOD] + }, + { + code: ` + export default Ember.Component({ + myFunc() { + const output = this.privateMethod(); + this.$elem.style.height1 = output.height; + } + });`, + options: ['height'] } ], invalid: [ @@ -109,7 +173,7 @@ ruleTester.run('no-jquery-methods', rule, { export default Ember.Component({ init() { const myJQueryObj = this.$(); - myJQueryObj[${BLACKLISTMETHOD}](); + myJQueryObj[${BLACKLISTMETHOD}](); } });`, options: [BLACKLISTMETHOD], @@ -130,6 +194,91 @@ ruleTester.run('no-jquery-methods', rule, { errors: [{ message: getMessage(BLACKLISTMETHOD) }] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$.add(); + } + });`, + options: [BLACKLISTMETHOD], + errors: [{ + message: getMessage(`$.${BLACKLISTMETHOD}()`) + }] + }, + { + code: ` + export default Ember.Component({ + init() { + Ember.$.add(); + } + });`, + options: [BLACKLISTMETHOD], + errors: [{ + message: getMessage(`$.${BLACKLISTMETHOD}()`) + }] + }, + { + code: ` + import jQ from 'jquery'; + const jayQuery = Ember.$; + + jayQuery('.class').add(); + jQ('.class2').add();`, + options: [BLACKLISTMETHOD], + errors: [ + { message: getMessage(BLACKLISTMETHOD) }, + { message: getMessage(BLACKLISTMETHOD) } + ] + }, + { + code: ` + function func1() { + const jayQuery = Bar.foo; + jayQuery().add(); + } + + function func2() { + const jayQuery = Ember.$; + jayQuery().add(); + } + + function func3() { + const jayQuery = Obj.method; + jayQuery().add(); + } + + function func4() { + const jayQuery = $; + jayQuery().add(); + } + + function func5() { + const jayQuery = Foo.bar; + jayQuery().add(); + }`, + options: [BLACKLISTMETHOD], + errors: [ + { message: getMessage(BLACKLISTMETHOD) }, + { message: getMessage(BLACKLISTMETHOD) } + ] + }, + { + code: ` + import jQ from 'jquery'; + function func1() { + const jayQuery = Ember.$; + jayQuery().add(); + } + + jQ().add(); + `, + options: [BLACKLISTMETHOD], + errors: [ + { message: getMessage(BLACKLISTMETHOD) }, + { message: getMessage(BLACKLISTMETHOD) } + ] } ] }); diff --git a/tests/lib/rules/no-jquery-selector.js b/tests/lib/rules/no-jquery-selector.js new file mode 100644 index 0000000..4963c8d --- /dev/null +++ b/tests/lib/rules/no-jquery-selector.js @@ -0,0 +1,145 @@ +const rule = require('../../../lib/rules/no-jquery-selector'); +const RuleTester = require('eslint').RuleTester; +const { + componentElementMessage, + getSelectorMessage +} = rule.meta; +const TEST_CLASS = '.some-class'; +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); + +ruleTester.run('no-jquery-selector', rule, { + valid: [ + { + code: ` + export default Ember.Component({ + init() { + Ember.$.ajax(); + } + });` + }, + { + code: ` + export default Ember.Component({ + init() { + this.$.ajax(); + } + });` + }, + { + code: ` + export default Ember.Component({ + myFunc() { + return \`\${someProp}\`; + } + });` + } + ], + invalid: [ + { + code: ` + export default Ember.Component({ + init() { + Ember.$('.some-class'); + } + });`, + errors: [ + { message: getSelectorMessage(TEST_CLASS) } + ] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$('.some-class'); + } + });`, + errors: [ + { message: getSelectorMessage(TEST_CLASS) } + ] + }, + { + code: ` + export default Ember.Component({ + myFunc(selector) { + Ember.$(selector); + } + });`, + errors: [ + { message: getSelectorMessage('selector') } + ] + }, + { + code: ` + export default Ember.Component({ + myFunc(selector) { + this.$(selector); + } + });`, + errors: [ + { message: getSelectorMessage('selector') } + ] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$(); + } + });`, + errors: [{ + message: componentElementMessage + }] + }, + { + code: ` + export default Ember.Component({ + init() { + this.$().focus(); + } + });`, + errors: [{ + message: componentElementMessage + }] + }, + { + code: ` + export default Ember.Component({ + init() { + Ember.$('.some-class').focus(); + } + });`, + errors: [{ + message: getSelectorMessage(TEST_CLASS) + }] + }, + { + code: ` + export default Ember.Component({ + myFunc() { + const myVar = '.some-class'; + this.$(myVar); + } + });`, + errors: [{ + message: getSelectorMessage('myVar') + }] + }, + { + code: ` + export default Ember.Component({ + myFunc() { + const myVar = '.some-class'; + Ember.$(myVar); + } + });`, + errors: [{ + message: getSelectorMessage('myVar') + }] + } + ] +}); diff --git a/tests/lib/rules/no-lifecycle-events.js b/tests/lib/rules/no-lifecycle-events.js index 257deb6..d9d5560 100644 --- a/tests/lib/rules/no-lifecycle-events.js +++ b/tests/lib/rules/no-lifecycle-events.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-lifecycle-events'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-lifecycle-events', rule, { valid: [ @@ -16,11 +21,20 @@ ruleTester.run('no-lifecycle-events', rule, { myCustomEvent: Ember.on('customEvent', function() { alert('sj08'); }) + });` + }, + { + code: ` + import Ember from 'ember'; + const { on } = $; + export default Ember.Component({ + registerFocus: on('click', function() { + // do stuff + }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + errors: [{ + message: MESSAGE + }] } ], invalid: [ @@ -33,10 +47,6 @@ ruleTester.run('no-lifecycle-events', rule, { }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -52,10 +62,6 @@ ruleTester.run('no-lifecycle-events', rule, { }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -70,10 +76,6 @@ ruleTester.run('no-lifecycle-events', rule, { }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -87,10 +89,6 @@ ruleTester.run('no-lifecycle-events', rule, { }); }.on('didInsertElement') });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-observers.js b/tests/lib/rules/no-observers.js index d91fc79..3dc3d47 100644 --- a/tests/lib/rules/no-observers.js +++ b/tests/lib/rules/no-observers.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-observers'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-observers', rule, { valid: [ @@ -15,11 +20,7 @@ ruleTester.run('no-observers', rule, { this.set('baz', true); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -32,10 +33,6 @@ ruleTester.run('no-observers', rule, { this.set('baz', true); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -50,10 +47,6 @@ ruleTester.run('no-observers', rule, { this.set('baz', true); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -69,10 +62,6 @@ ruleTester.run('no-observers', rule, { this.set('baz', true); }) })`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-send-action.js b/tests/lib/rules/no-send-action.js index 8c222b3..55a7c60 100644 --- a/tests/lib/rules/no-send-action.js +++ b/tests/lib/rules/no-send-action.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-send-action'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-send-action', rule, { valid: [ @@ -17,11 +22,7 @@ ruleTester.run('no-send-action', rule, { this.myAction(); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -34,10 +35,6 @@ ruleTester.run('no-send-action', rule, { } } });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/no-side-effect-cp.js b/tests/lib/rules/no-side-effect-cp.js index 27454f9..9de93a9 100644 --- a/tests/lib/rules/no-side-effect-cp.js +++ b/tests/lib/rules/no-side-effect-cp.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/no-side-effect-cp'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('no-side-efffect-cp', rule, { valid: [ @@ -15,13 +20,28 @@ ruleTester.run('no-side-efffect-cp', rule, { this.set('baz', true); } } - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` + }, + { + code: ` + import EmberObject from '@ember/object'; + export default EmberObject();` + }, + { + code: ` + import lodash from 'lodash'; + import Ember from 'ember'; + const { set } = lodash; + export default Ember.Component({ + foo: 'bar', + baz: false, + bar: Ember.computed('foo', function() { + set('baz', 'wat'); + }) + });` } ], + invalid: [ { code: ` @@ -32,10 +52,6 @@ ruleTester.run('no-side-efffect-cp', rule, { this.sendAction('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -49,10 +65,6 @@ ruleTester.run('no-side-efffect-cp', rule, { this.send('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -66,10 +78,6 @@ ruleTester.run('no-side-efffect-cp', rule, { this.sendEvent('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -83,10 +91,6 @@ ruleTester.run('no-side-efffect-cp', rule, { Ember.sendEvent('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -100,10 +104,6 @@ ruleTester.run('no-side-efffect-cp', rule, { Em.sendEvent('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -118,10 +118,6 @@ ruleTester.run('no-side-efffect-cp', rule, { sendEvent('baz') }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -135,10 +131,6 @@ ruleTester.run('no-side-efffect-cp', rule, { this.set('baz', 'wat'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -152,10 +144,6 @@ ruleTester.run('no-side-efffect-cp', rule, { Ember.set('baz', 'wat'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -169,10 +157,6 @@ ruleTester.run('no-side-efffect-cp', rule, { Em.set('baz', 'wat'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -187,10 +171,6 @@ ruleTester.run('no-side-efffect-cp', rule, { set('baz', 'wat'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -205,10 +185,6 @@ ruleTester.run('no-side-efffect-cp', rule, { ChadsSettingMcSetter('baz', 'wat'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -223,10 +199,6 @@ ruleTester.run('no-side-efffect-cp', rule, { setProperties({ baz: 'wat' }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -241,10 +213,6 @@ ruleTester.run('no-side-efffect-cp', rule, { SetAllTheThings({ baz: 'wat' }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -258,10 +226,6 @@ ruleTester.run('no-side-efffect-cp', rule, { this.setProperties({ baz: 'wat' }); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -275,10 +239,55 @@ ruleTester.run('no-side-efffect-cp', rule, { this.trigger('suchTrigger'); }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, + errors: [{ + message: MESSAGE + }] + }, + // This test must come before any others that `import Ember from 'ember'` + // to illustrate the problem of the import binding being saved in module + // scope. + // see #91 + { + code: ` + import Ember from 'ember'; + import SomethingElse from 'something-else'; + const { set } = Ember; + export default Ember.Component({ + foo: 'bar', + baz: false, + bar: Ember.computed('foo', function() { + set('baz', 'wat'); + }) + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + import Ember from 'ember'; + const { set } = Ember; + export default Ember.Component({ + foo: 'bar', + baz: false, + bar: Ember.computed('foo', function() { + set('baz', 'wat'); + }) + });`, + errors: [{ + message: MESSAGE + }] + }, + { + code: ` + import E from 'ember'; + export default E.Component({ + foo: 'bar', + baz: false, + bar: E.computed('foo', function() { + E.set('baz', 'wat'); + }) + });`, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/require-dependent-keys.js b/tests/lib/rules/require-dependent-keys.js index 3cd1afe..e96e6f6 100644 --- a/tests/lib/rules/require-dependent-keys.js +++ b/tests/lib/rules/require-dependent-keys.js @@ -1,7 +1,12 @@ const rule = require('../../../lib/rules/require-dependent-keys'); const MESSAGE = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); ruleTester.run('require-dependent-keys', rule, { valid: [ @@ -11,11 +16,7 @@ ruleTester.run('require-dependent-keys', rule, { foo: Ember.computed('bar', function() { return this.get('bar') * 2; }) - });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - } + });` } ], invalid: [ @@ -26,10 +27,6 @@ ruleTester.run('require-dependent-keys', rule, { return this.get('bar') * 2; }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] @@ -41,10 +38,6 @@ ruleTester.run('require-dependent-keys', rule, { return this.get('bar') * 2; }) });`, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module' - }, errors: [{ message: MESSAGE }] diff --git a/tests/lib/rules/require-ember-lifeline.js b/tests/lib/rules/require-ember-lifeline.js index d3bd932..8080581 100644 --- a/tests/lib/rules/require-ember-lifeline.js +++ b/tests/lib/rules/require-ember-lifeline.js @@ -1,15 +1,19 @@ const rule = require('../../../lib/rules/require-ember-lifeline'); const getMessage = rule.meta.message; const RuleTester = require('eslint').RuleTester; -const ruleTester = new RuleTester(); -const parserOptions = { - ecmaVersion: 6, - sourceType: 'module' -}; +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}); +const filename = 'addon/components/my-component.js'; +const unlintableFilename = 'addon/utils/util.js'; ruleTester.run('require-ember-lifeline', rule, { valid: [ { + filename, code: ` import Ember from 'ember'; @@ -19,10 +23,10 @@ ruleTester.run('require-ember-lifeline', rule, { doSomeWork(); }); } - });`, - parserOptions + });` }, { + filename, code: ` import Ember from 'ember'; @@ -34,10 +38,10 @@ ruleTester.run('require-ember-lifeline', rule, { }); } } - });`, - parserOptions + });` }, { + filename, code: ` import Ember from 'ember'; @@ -47,10 +51,10 @@ ruleTester.run('require-ember-lifeline', rule, { doSomeWork(); }); } - });`, - parserOptions + });` }, { + filename, code: ` import Ember from 'ember'; @@ -62,10 +66,10 @@ ruleTester.run('require-ember-lifeline', rule, { }); } } - });`, - parserOptions + });` }, { + filename, code: ` import Ember from 'ember'; @@ -75,10 +79,10 @@ ruleTester.run('require-ember-lifeline', rule, { doSomeWork(); }); } - });`, - parserOptions + });` }, { + filename, code: ` import Ember from 'ember'; @@ -90,12 +94,27 @@ ruleTester.run('require-ember-lifeline', rule, { }); } } - });`, - parserOptions + });` + }, + { + filename: unlintableFilename, + code: ` + import Ember from 'ember'; + + export default Ember.Component({ + actions: { + foo() { + Ember.run.later(() => { + doSomeWork(); + }); + } + } + });` } ], invalid: [ { + filename, code: ` import Ember from 'ember'; @@ -106,13 +125,13 @@ ruleTester.run('require-ember-lifeline', rule, { }, 100); } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.later') }] }, { - code: ` + filename, + code: ` import Ember from 'ember'; export default Ember.Component({ @@ -124,12 +143,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.later') }] }, { + filename, code: ` import Ember from 'ember'; @@ -140,12 +159,12 @@ ruleTester.run('require-ember-lifeline', rule, { }, 100); } });`, - parserOptions, errors: [{ message: getMessage('run.later') }] }, { + filename, code: ` import Ember from 'ember'; @@ -158,12 +177,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('run.later') }] }, { + filename, code: ` import Ember from 'ember'; @@ -178,12 +197,12 @@ ruleTester.run('require-ember-lifeline', rule, { }, 100); } });`, - parserOptions, errors: [{ message: getMessage('foo.later') }] }, { + filename, code: ` import Ember from 'ember'; @@ -200,12 +219,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('foo.later') }] }, { + filename, code: ` import Ember from 'ember'; @@ -216,13 +235,13 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.next') }] }, { - code: ` + filename, + code: ` import Ember from 'ember'; export default Ember.Component({ @@ -234,12 +253,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.next') }] }, { + filename, code: ` import Ember from 'ember'; @@ -250,12 +269,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('run.next') }] }, { + filename, code: ` import Ember from 'ember'; @@ -268,12 +287,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('run.next') }] }, { + filename, code: ` import Ember from 'ember'; @@ -288,12 +307,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('foo.next') }] }, { + filename, code: ` import Ember from 'ember'; @@ -310,12 +329,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('foo.next') }] }, { + filename, code: ` import Ember from 'ember'; @@ -326,13 +345,13 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.debounce') }] }, { - code: ` + filename, + code: ` import Ember from 'ember'; export default Ember.Component({ @@ -344,12 +363,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.debounce') }] }, { + filename, code: ` import Ember from 'ember'; @@ -360,12 +379,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('run.debounce') }] }, { + filename, code: ` import Ember from 'ember'; @@ -378,12 +397,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('run.debounce') }] }, { + filename, code: ` import Ember from 'ember'; @@ -398,12 +417,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('foo.debounce') }] }, { + filename, code: ` import Ember from 'ember'; @@ -420,12 +439,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('foo.debounce') }] }, { + filename, code: ` import Ember from 'ember'; @@ -436,13 +455,13 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.throttle') }] }, { - code: ` + filename, + code: ` import Ember from 'ember'; export default Ember.Component({ @@ -454,12 +473,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('Ember.run.throttle') }] }, { + filename, code: ` import Ember from 'ember'; @@ -470,12 +489,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('run.throttle') }] }, { + filename, code: ` import Ember from 'ember'; @@ -488,12 +507,12 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('run.throttle') }] }, { + filename, code: ` import Ember from 'ember'; @@ -508,12 +527,12 @@ ruleTester.run('require-ember-lifeline', rule, { }); } });`, - parserOptions, errors: [{ message: getMessage('foo.throttle') }] }, { + filename, code: ` import Ember from 'ember'; @@ -530,7 +549,6 @@ ruleTester.run('require-ember-lifeline', rule, { } } });`, - parserOptions, errors: [{ message: getMessage('foo.throttle') }] diff --git a/tests/lib/utils/jquery.js b/tests/lib/utils/jquery.js new file mode 100644 index 0000000..92c48c4 --- /dev/null +++ b/tests/lib/utils/jquery.js @@ -0,0 +1,107 @@ +const assert = require('assert'); +const { + isJQueryCallee, + isVariableAssigmentGlobalJquery, + isVariableAssigmentEmberJquery, + isVariableAssigmentJquery +} = require('../../../lib/utils/jquery'); +const { + jqueryNodes, + emberJqueryNodes, + notJqueryNodes +} = require('../../mocks/jquery-mocks'); + +describe('jquery utils', function() { + describe('isJQueryCallee', function() { + it('should return true when the callee is global jQuery', function() { + let node = jqueryNodes['object.callee.property.name']; + assert.equal(isJQueryCallee(node), true); + + node = jqueryNodes['object.name']; + assert.equal(isJQueryCallee(node), true); + + node = jqueryNodes['object.callee.name']; + assert.equal(isJQueryCallee(node), true); + }); + + it('should return true when the callee is local jQuery', function() { + const localJqueryArray = ['local$', 'localJayQuery']; + let node = Object.assign({}, jqueryNodes['object.callee.property.name']); + node.object.callee.property.name = 'local$'; + assert.equal(isJQueryCallee(node, localJqueryArray), true); + + node = Object.assign({}, jqueryNodes['object.name']); + node.object.name = 'local$'; + assert.equal(isJQueryCallee(node, localJqueryArray), true); + + node = Object.assign({}, jqueryNodes['object.callee.name']); + node.object.callee.name = 'local$'; + assert.equal(isJQueryCallee(node, localJqueryArray), true); + }); + + it('should return false when the callee is not jQuery', function() { + const localJqueryArray = ['local$', 'localJayQuery']; + let node = notJqueryNodes['object.callee.property.name']; + assert.equal(isJQueryCallee(node, localJqueryArray), false); + + node = notJqueryNodes['object.name']; + assert.equal(isJQueryCallee(node, localJqueryArray), false); + + node = notJqueryNodes['object.callee.name']; + assert.equal(isJQueryCallee(node, localJqueryArray), false); + }); + }); + + describe('isVariableAssigmentGlobalJquery', function() { + it('should return true when the var assignment is global jQuery', function() { + const node = jqueryNodes['init.name']; + assert.equal(isVariableAssigmentGlobalJquery(node), true); + }); + it('should return false when the var assignment is not jQuery', function() { + const node = notJqueryNodes['init.name']; + assert.equal(isVariableAssigmentGlobalJquery(node), false); + }); + it('should return false when the var assignment is Ember jQuery', function() { + const node = emberJqueryNodes['init.callee.property.name']; + assert.equal(isVariableAssigmentGlobalJquery(node), false); + }); + }); + + describe('isVariableAssigmentEmberJquery', function() { + it('should return true when the var assignment is Ember jQuery', function() { + const node = emberJqueryNodes['init.callee.property.name']; + assert.equal(isVariableAssigmentEmberJquery(node), true); + }); + it('should return false when the var assignment is global jQuery', function() { + const node = jqueryNodes['init.name']; + assert.equal(isVariableAssigmentEmberJquery(node), false); + }); + it('should return false when the var assignment is not jQuery', function() { + const node = notJqueryNodes['init.name']; + assert.equal(isVariableAssigmentEmberJquery(node), false); + }); + }); + + describe('isVariableAssigmentJquery', function() { + it('should return true when the var assignment\'s name is jQuery', function() { + let node = jqueryNodes['init.callee.property.name']; + assert.equal(isVariableAssigmentJquery(node), true); + + node = jqueryNodes['init.name']; + assert.equal(isVariableAssigmentJquery(node), true); + + node = jqueryNodes['init.property.name']; + assert.equal(isVariableAssigmentJquery(node), true); + }); + it('should return false when the var assignment\'s name is not jQuery', function() { + let node = notJqueryNodes['init.callee.property.name']; + assert.equal(isVariableAssigmentJquery(node), false); + + node = notJqueryNodes['init.name']; + assert.equal(isVariableAssigmentJquery(node), false); + + node = notJqueryNodes['init.property.name']; + assert.equal(isVariableAssigmentJquery(node), false); + }); + }); +}); diff --git a/tests/mocks/jquery-mocks.js b/tests/mocks/jquery-mocks.js new file mode 100644 index 0000000..66d9899 --- /dev/null +++ b/tests/mocks/jquery-mocks.js @@ -0,0 +1,120 @@ +const jqueryNodes = { + 'init.callee.property.name': { + init: { + callee: { + property: { + name: '$' + } + } + } + }, + 'init.name': { + init: { + name: '$' + } + }, + 'init.property.name': { + init: { + property: { + name: 'jQuery' + } + } + }, + 'object.callee.property.name': { + object: { + callee: { + property: { + name: '$' + } + } + } + }, + 'object.callee.name': { + object: { + callee: { + name: '$' + } + } + }, + 'object.name': { + object: { + name: '$' + } + } +}; + +const emberJqueryNodes = { + 'init.callee.property.name': { + init: { + callee: { + object: { + name: 'Ember' + }, + property: { + name: '$' + } + } + } + }, + 'init.property.name': { + init: { + object: { + name: 'Ember' + }, + property: { + name: 'jQuery' + } + } + } +}; + +const notJqueryNodes = { + 'init.callee.property.name': { + init: { + callee: { + property: { + name: 'not$' + } + } + } + }, + 'init.name': { + init: { + name: 'not$' + } + }, + 'init.property.name': { + init: { + property: { + name: 'notJquery' + } + } + }, + 'object.callee.property.name': { + object: { + callee: { + property: { + name: 'not$' + } + } + } + }, + 'object.callee.name': { + object: { + callee: { + name: '$nope' + } + } + }, + 'object.name': { + object: { + name: 'notJquery' + } + } +}; + +module.exports = { + jqueryNodes, + emberJqueryNodes, + notJqueryNodes +}; diff --git a/yarn.lock b/yarn.lock index 5001a54..d9584a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,5 +1,7 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 + + acorn-jsx@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" @@ -97,14 +99,14 @@ asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" -assert-plus@^1.0.0, assert-plus@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -196,10 +198,14 @@ babel-types@^6.18.0, babel-types@^6.24.1: lodash "^4.2.0" to-fast-properties "^1.0.1" -babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: +babylon@^6.11.0, babylon@^6.15.0: version "6.17.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.0.tgz#37da948878488b9c4e3c4038893fa3314b3fc932" +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + balanced-match@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -265,6 +271,10 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + caseless@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" @@ -330,7 +340,7 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@^2.9.0, commander@2.9.0: +commander@2.9.0, commander@^2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" dependencies: @@ -381,6 +391,14 @@ cross-spawn@^4: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -403,6 +421,12 @@ debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + debug@^2.1.1: version "2.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" @@ -415,12 +439,6 @@ debug@^2.2.0, debug@^2.6.3: dependencies: ms "0.7.3" -debug@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -516,7 +534,7 @@ es6-set@~0.1.3: es6-symbol "3" event-emitter "~0.3.4" -es6-symbol@~3.1, es6-symbol@~3.1.0, es6-symbol@3: +es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" dependencies: @@ -532,7 +550,7 @@ es6-weak-map@^2.0.1: es6-iterator "2" es6-symbol "3" -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -621,6 +639,18 @@ event-emitter@~0.3.4: d "~0.1.1" es5-ext "~0.10.7" +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -691,13 +721,19 @@ find-cache-dir@^0.1.1: mkdirp "^0.5.1" pkg-dir "^1.0.0" -find-up@^1.0.0, find-up@^1.1.2: +find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" dependencies: path-exists "^2.0.0" pinkie-promise "^2.0.0" +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + flat-cache@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.1.tgz#6c837d6225a7de5659323740b36d5361f71691ff" @@ -717,7 +753,7 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -foreground-child@^1.3.3, foreground-child@^1.5.3: +foreground-child@^1.5.3, foreground-child@^1.5.6: version "1.5.6" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" dependencies: @@ -754,6 +790,10 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -780,9 +820,9 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" +glob@7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -791,9 +831,9 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.5.tgz#b4202a69099bbb4d292a7c1b95b6682b67ebdc95" +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: + version "7.1.1" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -898,7 +938,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@~2.0.1, inherits@2: +inherits@2, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -1037,6 +1077,10 @@ is-resolvable@^1.0.0: dependencies: tryit "^1.0.1" +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -1045,7 +1089,7 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" -isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -1063,50 +1107,50 @@ isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" -istanbul-lib-coverage@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#caca19decaef3525b5d6331d701f3f3b7ad48528" +istanbul-lib-coverage@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" -istanbul-lib-hook@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.6.tgz#c0866d1e81cf2d5319249510131fc16dee49231f" +istanbul-lib-hook@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.1.tgz#169e31bc62c778851a99439dd99c3cc12184d360" +istanbul-lib-instrument@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" - babylon "^6.13.0" - istanbul-lib-coverage "^1.1.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.1.1" semver "^5.3.0" -istanbul-lib-report@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.0.tgz#444c4ecca9afa93cf584f56b10f195bf768c0770" +istanbul-lib-report@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" dependencies: - istanbul-lib-coverage "^1.1.0" + istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.0.tgz#8c7706d497e26feeb6af3e0c28fd5b0669598d0e" +istanbul-lib-source-maps@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" dependencies: debug "^2.6.3" - istanbul-lib-coverage "^1.1.0" + istanbul-lib-coverage "^1.1.1" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.0.tgz#1ef3b795889219cfb5fad16365f6ce108d5f8c66" +istanbul-reports@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" dependencies: handlebars "^4.0.3" @@ -1124,16 +1168,16 @@ js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" -js-yaml@^3.5.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" +js-yaml@3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" dependencies: argparse "^1.0.7" esprima "^2.6.0" -js-yaml@3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" +js-yaml@^3.5.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" dependencies: argparse "^1.0.7" esprima "^2.6.0" @@ -1218,6 +1262,22 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -1300,6 +1360,12 @@ md5-o-matic@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + merge-source-map@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.3.tgz#da1415f2722a5119db07b14c4f973410863a2abf" @@ -1334,17 +1400,17 @@ mime-types@^2.1.12, mime-types@~2.1.7: dependencies: mime-db "~1.27.0" +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + minimatch@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" dependencies: brace-expansion "^1.0.0" -minimist@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de" - -minimist@~0.0.1, minimist@0.0.8: +minimist@0.0.8, minimist@~0.0.1: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -1352,7 +1418,11 @@ minimist@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@0.5.1: +minimist@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de" + +mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -1418,13 +1488,19 @@ normalize-path@^2.0.1: dependencies: remove-trailing-separator "^1.0.1" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" -nyc@^10.0.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-10.3.0.tgz#a7051ac03f89d17e719a586a66a84ce4bdfde857" +nyc@^11.2.1: + version "11.2.1" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.2.1.tgz#ad850afe9dbad7f4970728b4b2e47fed1c38721c" dependencies: archy "^1.0.0" arrify "^1.0.1" @@ -1433,15 +1509,15 @@ nyc@^10.0.0: debug-log "^1.0.1" default-require-extensions "^1.0.0" find-cache-dir "^0.1.1" - find-up "^1.1.2" + find-up "^2.1.0" foreground-child "^1.5.3" glob "^7.0.6" - istanbul-lib-coverage "^1.1.0" - istanbul-lib-hook "^1.0.6" - istanbul-lib-instrument "^1.7.1" - istanbul-lib-report "^1.1.0" - istanbul-lib-source-maps "^1.2.0" - istanbul-reports "^1.1.0" + istanbul-lib-coverage "^1.1.1" + istanbul-lib-hook "^1.0.7" + istanbul-lib-instrument "^1.8.0" + istanbul-lib-report "^1.1.1" + istanbul-lib-source-maps "^1.2.1" + istanbul-reports "^1.1.1" md5-hex "^1.2.0" merge-source-map "^1.0.2" micromatch "^2.3.11" @@ -1449,9 +1525,9 @@ nyc@^10.0.0: resolve-from "^2.0.0" rimraf "^2.5.4" signal-exit "^3.0.1" - spawn-wrap "1.2.4" - test-exclude "^4.1.0" - yargs "^7.1.0" + spawn-wrap "^1.3.8" + test-exclude "^4.1.1" + yargs "^8.0.1" yargs-parser "^5.0.0" oauth-sign@~0.8.1: @@ -1501,11 +1577,27 @@ os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" dependencies: + execa "^0.7.0" lcid "^1.0.0" + mem "^1.1.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" parse-glob@^3.0.4: version "3.0.4" @@ -1528,6 +1620,10 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -1536,6 +1632,10 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -1548,6 +1648,12 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -1614,6 +1720,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -1622,6 +1735,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" @@ -1771,7 +1892,7 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -semver@^5.3.0, "semver@2 || 3 || 4 || 5": +"semver@2 || 3 || 4 || 5", semver@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -1779,6 +1900,16 @@ set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + shelljs@^0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.5.tgz#2eef7a50a21e1ccf37da00df767ec69e30ad0675" @@ -1787,11 +1918,7 @@ shelljs@^0.7.5: interpret "^1.0.0" rechoir "^0.6.2" -signal-exit@^2.0.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-2.1.2.tgz#375879b1f92ebc3b334480d038dc546a6d558564" - -signal-exit@^3.0.0, signal-exit@^3.0.1: +signal-exit@^3.0.0, signal-exit@^3.0.1, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -1819,15 +1946,15 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" -spawn-wrap@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.2.4.tgz#920eb211a769c093eebfbd5b0e7a5d2e68ab2e40" +spawn-wrap@^1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.8.tgz#fa2a79b990cbb0bb0018dca6748d88367b19ec31" dependencies: - foreground-child "^1.3.3" + foreground-child "^1.5.6" mkdirp "^0.5.0" os-homedir "^1.0.1" rimraf "^2.3.3" - signal-exit "^2.0.0" + signal-exit "^3.0.2" which "^1.2.4" spdx-correct@~1.0.0: @@ -1863,11 +1990,7 @@ sshpk@^1.7.0: jsbn "~0.1.0" tweetnacl "~0.14.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" dependencies: @@ -1882,6 +2005,10 @@ string-width@^2.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^3.0.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -1902,20 +2029,24 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + strip-json-comments@~1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - -supports-color@^3.1.2, supports-color@3.1.2: +supports-color@3.1.2, supports-color@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" dependencies: has-flag "^1.0.0" +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + table@^3.7.8: version "3.8.3" resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" @@ -1927,9 +2058,9 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" -test-exclude@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.0.tgz#04ca70b7390dd38c98d4a003a173806ca7991c91" +test-exclude@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" dependencies: arrify "^1.0.1" micromatch "^2.3.11" @@ -2021,9 +2152,9 @@ verror@1.3.6: dependencies: extsprintf "1.0.2" -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" which@^1.2.4, which@^1.2.9: version "1.2.14" @@ -2035,6 +2166,10 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" @@ -2043,10 +2178,6 @@ wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -2090,23 +2221,29 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" -yargs@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" dependencies: - camelcase "^3.0.0" + camelcase "^4.1.0" + +yargs@^8.0.1: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" cliui "^3.2.0" decamelize "^1.1.1" get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" require-directory "^2.1.1" require-main-filename "^1.0.1" set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" + string-width "^2.0.0" + which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^5.0.0" + yargs-parser "^7.0.0" yargs@~1.2.6: version "1.2.6" @@ -2122,4 +2259,3 @@ yargs@~3.10.0: cliui "^2.1.0" decamelize "^1.0.0" window-size "0.1.0" -