Skip to content

Commit

Permalink
Merge pull request #60 from ilyavolodin/v2
Browse files Browse the repository at this point in the history
Update for version 2.0
  • Loading branch information
ilyavolodin committed Mar 25, 2016
2 parents 603106a + dc4858a commit 56622be
Show file tree
Hide file tree
Showing 22 changed files with 754 additions and 579 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"no-nested-ternary": 2,
"no-undefined": 2,
"radix": 2,
"space-after-keywords": [2, "always"],
"keyword-spacing": 2,
"no-multi-spaces": 2,
"valid-jsdoc": [2, {
"prefer": {
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ npm install eslint-plugin-backbone --save-dev

## Default configuration

** Deprecated in v2 **

**node:** ESLint v2 removed support for default configurations. Please see config below for details.

If you are using ESLint >0.9.0 then this plugin will provide default configuration. If you are fine with defaults, you do not need to update your .eslintrc file.

Defaults are currently set to the following:
Expand All @@ -56,9 +60,24 @@ Defaults are currently set to the following:
"no-view-collection-models": 2,
"no-view-model-attributes": 2,
"no-view-onoff-binding": 2,
"no-view-qualified-jquery": 0,
"render-return": 2
```

## Configuration

In version 2.0.0 removed support for default configurations for plugins and replaced it with ability for plugins to bundle configs. This plugin include `recommended`
configuration that you can extend from to enable recommended setup of the rules (see "Default configuration" for the list of enabled rules).

To enable bundled config modify your .eslintrc file to include the following line:

```json
{
"extends": "plugin:backbone/recommended"
}

This will enable all of the rules listed above, as well as add two global variables - `Backbone` and `_`.

## Modify .eslintrc for your project

Add `plugins` section and specify eslint-plugin-backbone as a plugin
Expand Down
50 changes: 31 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,36 @@ module.exports = {
"no-view-qualified-jquery": require("./lib/rules/no-view-qualified-jquery"),
"render-return": require("./lib/rules/render-return")
},
rulesConfig: {
"collection-model": 2,
"defaults-on-top": 1,
"event-scope": 1,
"events-on-top": [1, ["tagName", "className"]],
"initialize-on-top": [1, { View: ["tagName", "className", "events"], Model: ["defaults", "url", "urlRoot"], Collection: ["model", "url"] }],
"model-defaults": 2,
"no-changed-set": 2,
"no-collection-models": 2,
"no-constructor": 1,
"no-el-assign": 2,
"no-model-attributes": 2,
"no-native-jquery": [1, "selector"],
"no-silent": 1,
"no-view-collection-models": 2,
"no-view-model-attributes": 2,
"no-view-onoff-binding": 2,
"no-view-qualified-jquery": 0,
"render-return": 2
configs: {
recommended: {
env: ["browser"],
globals: {
"Backbone": false,
"_": false
},
plugins: [
"backbone"
],
rules: {
"backbone/collection-model": 2,
"backbone/defaults-on-top": 1,
"backbone/event-scope": 1,
"backbone/events-on-top": [1, ["tagName", "className"]],
"backbone/initialize-on-top": [1, { View: ["tagName", "className", "events"], Model: ["defaults", "url", "urlRoot"], Collection: ["model", "url"] }],
"backbone/model-defaults": 2,
"backbone/no-changed-set": 2,
"backbone/no-collection-models": 2,
"backbone/no-constructor": 1,
"backbone/no-el-assign": 2,
"backbone/no-model-attributes": 2,
"backbone/no-native-jquery": [1, "selector"],
"backbone/no-silent": 1,
"backbone/no-view-collection-models": 2,
"backbone/no-view-model-attributes": 2,
"backbone/no-view-onoff-binding": 2,
"backbone/no-view-qualified-jquery": 0,
"backbone/render-return": 2
}
}
}
};
65 changes: 36 additions & 29 deletions lib/rules/collection-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,45 @@

var helper = require("../backbone-helper.js");

module.exports = function(context) {

var settings = context.settings || /* istanbul ignore next */ {};
var backboneCollection = [];
module.exports = {
meta: {
docs: {
description: "Require all collections to declare model",
category: "Best Practices",
recommended: true
},
schema: []
},
create: function(context) {
var settings = context.settings || /* istanbul ignore next */ {};
var backboneCollection = [];

//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------

return {
"CallExpression": function(node) {
if (helper.isBackboneCollection(node, settings.backbone)) {
backboneCollection.push({ node: node, found: false });
}
},
"CallExpression:exit": function(node) {
var lastCollection = backboneCollection[backboneCollection.length - 1];
if (backboneCollection.length > 0 && lastCollection.node === node) {
if (!lastCollection.found) {
context.report(node, "All collections should have model declared");
return {
"CallExpression": function(node) {
if (helper.isBackboneCollection(node, settings.backbone)) {
backboneCollection.push({ node: node, found: false });
}
backboneCollection.pop();
}
},
"Identifier": function(node) {
if (backboneCollection.length > 0 && node.name === "model") {
if (helper.checkIfPropertyInBackboneCollection(node, settings.backbone)) {
backboneCollection[backboneCollection.length - 1].found = true;
},
"CallExpression:exit": function(node) {
var lastCollection = backboneCollection[backboneCollection.length - 1];
if (backboneCollection.length > 0 && lastCollection.node === node) {
if (!lastCollection.found) {
context.report(node, "All collections should have model declared");
}
backboneCollection.pop();
}
},
"Identifier": function(node) {
if (backboneCollection.length > 0 && node.name === "model") {
if (helper.checkIfPropertyInBackboneCollection(node, settings.backbone)) {
backboneCollection[backboneCollection.length - 1].found = true;
}
}
}
}
};
};
}
};

module.exports.schema = [];
78 changes: 43 additions & 35 deletions lib/rules/defaults-on-top.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,55 @@

var helper = require("../backbone-helper.js");

module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "Require defaults to be on top of the model",
category: "Stylistic",
recommended: false
},
schema: [
{
"type": "array",
"items": {
"type": "string",
"uniqueItems": true
},
"additionalProperties": false
}
]
},
create: function(context) {

var settings = context.settings || /* istanbul ignore next */ {};
var settings = context.settings || /* istanbul ignore next */ {};

//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------

return {
"Identifier": function(node) {
if (node.name === "defaults") {
var parent = node.parent, grandparent = parent.parent;
if (helper.checkIfPropertyInBackboneModel(node, settings.backbone) && grandparent.type === "ObjectExpression" && grandparent.properties[0] !== parent) {
if (context.options && context.options.length > 0) {
//we have exceptions passed into the rule, verify that any property that goes before events is in the options list
for (var i = 0, l = grandparent.properties.length; i < l; i++) {
if (grandparent.properties[i] === parent) {
break;
}
if ((grandparent.properties[i].key.type === "Identifier" && context.options[0].indexOf(grandparent.properties[i].key.name) === -1) ||
(grandparent.properties[i].key.type === "Literal" && context.options[0].indexOf(grandparent.properties[i].key.value) === -1)) {
context.report(node, "defaults should be declared at the top of the model.");
break;
return {
"Identifier": function(node) {
if (node.name === "defaults") {
var parent = node.parent, grandparent = parent.parent;
if (helper.checkIfPropertyInBackboneModel(node, settings.backbone) && grandparent.type === "ObjectExpression" && grandparent.properties[0] !== parent) {
if (context.options && context.options.length > 0) {
//we have exceptions passed into the rule, verify that any property that goes before events is in the options list
for (var i = 0, l = grandparent.properties.length; i < l; i++) {
if (grandparent.properties[i] === parent) {
break;
}
if ((grandparent.properties[i].key.type === "Identifier" && context.options[0].indexOf(grandparent.properties[i].key.name) === -1) ||
(grandparent.properties[i].key.type === "Literal" && context.options[0].indexOf(grandparent.properties[i].key.value) === -1)) {
context.report(node, "defaults should be declared at the top of the model.");
break;
}
}
} else {
context.report(node, "defaults should be declared at the top of the model.");
}
} else {
context.report(node, "defaults should be declared at the top of the model.");
}
}
}
}
};
};

module.exports.schema = [
{
"type": "array",
"items": {
"type": "string",
"uniqueItems": true
},
"additionalProperties": false
};
}
];
};
71 changes: 40 additions & 31 deletions lib/rules/event-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,51 @@

var helper = require("../backbone-helper.js");

module.exports = function(context) {
module.exports = {
meta: {
docs: {
description: "Verify that scope is passed into event handlers",
category: "Best Practices",
recommended: false
},
schema: []
},
create: function(context) {

var settings = context.settings || /* istanbul ignore next */ {};
var backbone = [];
var settings = context.settings || /* istanbul ignore next */ {};
var backbone = [];

//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------

return {
"CallExpression": function(node) {
backbone.push(backbone[backbone.length - 1] || helper.isBackboneAny(node, settings.backbone));
},
"CallExpression:exit": function(node) {
if (helper.isBackboneAny(node, settings.backbone)) {
backbone.pop();
}
},
"Identifier": function(node) {
var ancestors = context.getAncestors(node);
var parent = ancestors.pop();
if (backbone[backbone.length - 1] && (node.name === "on" || node.name === "once") &&
!((parent.object.type === "CallExpression" && ((parent.object.callee.type === "Identifier" && parent.object.callee.name === "$") || //covers the case of $().on()
(parent.object.callee.type === "MemberExpression" && parent.object.callee.object.type === "ThisExpression" && parent.object.callee.property.name === "$")) || //covers the case of this.$().on()
(parent.object.type === "MemberExpression" && parent.object.property.type === "Identifier" && parent.object.property.name === "$el")))) { //covers the case of this.$el.on()
while (parent.type !== "CallExpression" && !parent.arguments) {
parent = ancestors.pop();
return {
"CallExpression": function(node) {
backbone.push(backbone[backbone.length - 1] || helper.isBackboneAny(node, settings.backbone));
},
"CallExpression:exit": function(node) {
if (helper.isBackboneAny(node, settings.backbone)) {
backbone.pop();
}
if (parent.arguments.length === 1 && parent.arguments[0].type === 'ObjectExpression') {
context.report(node, "Pass scope as second argument.");
} else if (parent.arguments.length < 3 && (parent.arguments[0] || {}).type !== 'ObjectExpression') {
context.report(node, "Pass scope as third argument.");
},
"Identifier": function(node) {
var ancestors = context.getAncestors(node);
var parent = ancestors.pop();
if (backbone[backbone.length - 1] && (node.name === "on" || node.name === "once") &&
!((parent.object.type === "CallExpression" && ((parent.object.callee.type === "Identifier" && parent.object.callee.name === "$") || //covers the case of $().on()
(parent.object.callee.type === "MemberExpression" && parent.object.callee.object.type === "ThisExpression" && parent.object.callee.property.name === "$")) || //covers the case of this.$().on()
(parent.object.type === "MemberExpression" && parent.object.property.type === "Identifier" && parent.object.property.name === "$el")))) { //covers the case of this.$el.on()
while (parent.type !== "CallExpression" && !parent.arguments) {
parent = ancestors.pop();
}
if (parent.arguments.length === 1 && parent.arguments[0].type === 'ObjectExpression') {
context.report(node, "Pass scope as second argument.");
} else if (parent.arguments.length < 3 && (parent.arguments[0] || {}).type !== 'ObjectExpression') {
context.report(node, "Pass scope as third argument.");
}
}
}
}
};
};
}
};

module.exports.schema = [];
Loading

0 comments on commit 56622be

Please sign in to comment.