Skip to content

Commit 5173618

Browse files
committed
Merge pull request #176 from Gillespie59/development
0.7.0
2 parents 9a37f4e + 8d818e3 commit 5173618

File tree

6 files changed

+275
-2
lines changed

6 files changed

+275
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ We provide also three samples :
8686
| 'ng_document_service': 2 | Instead of the default document object, you should prefer the AngularJS wrapper service $document. [Y180](https://github.com/johnpapa/angular-styleguide#style-y180) |
8787
| 'ng_empty_controller': 0 | If you have one empty controller, maybe you have linked it in your Router configuration or in one of your views. You can remove this declaration because this controller is useless |
8888
| 'ng_foreach': 0 | You should use the angular.forEach method instead of the default JavaScript implementation [].forEach. |
89+
| 'ng_file_name': 0 | All your file names should match the angular component name. The second parameter can be a config object [2, {nameStyle: 'dash', typeSeparator: 'dot', ignoreTypeSuffix: true}] to match `avenger-profile.directive.js` or `avanger-api.service.js`. Possible values for `typeSeparator` and `nameStyle` are `dot`, `dash` and `underscore`. The options `ignoreTypeSuffix` ignores camel cased suffixes like `someController` or `myService`. [Y120](https://github.com/johnpapa/angular-styleguide#style-y120) [Y121](https://github.com/johnpapa/angular-styleguide#style-y121) |
8990
| 'ng_filter_name': 0 | All your filters should have a name starting with the parameter you can define in your config object. The second parameter can be a Regexp wrapped in quotes. ("ng_filter_name": [2, "ng"]) |
9091
| 'ng_function_type': 0 | Anonymous or named functions inside AngularJS components. The first parameter sets which type of function is required and can be 'named' or 'anonymous'. The second parameter is an optional list of angular object names. [Y024](https://github.com/johnpapa/angular-styleguide/blob/master/README.md#style-y024) |
9192
| 'ng_interval_service': 2 | Instead of the default setInterval function, you should use the AngularJS wrapper service $interval [Y181](https://github.com/johnpapa/angular-styleguide#style-y181) |

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
'ng_directive_name': require('./rules/ng_directive_name'),
1616
'ng_document_service': require('./rules/ng_document_service'),
1717
'ng_empty_controller': require('./rules/ng_empty_controller'),
18+
'ng_file_name': require('./rules/ng_file_name'),
1819
'ng_filter_name': require('./rules/ng_filter_name'),
1920
'ng_foreach': require('./rules/ng_foreach'),
2021
'ng_function_type': require('./rules/ng_function_type'),
@@ -58,6 +59,7 @@
5859
'ng_document_service': 2,
5960
'ng_empty_controller': 0,
6061
'ng_foreach': 0,
62+
'ng_file_name': 0,
6163
'ng_filter_name': 0,
6264
'ng_function_type': 0,
6365
'ng_interval_service': 2,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-angular",
3-
"version": "0.6.1",
3+
"version": "0.7.0",
44
"description": "ESLint rules for AngularJS projects",
55
"main": "index.js",
66
"repository": {

rules/ng_file_name.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
module.exports = (function () {
2+
'use strict';
3+
4+
var utils = require('./utils/utils');
5+
var path = require('path');
6+
var fileEnding = '.js';
7+
8+
var separators = {
9+
dot: '.',
10+
dash: '-',
11+
underscore: '_'
12+
};
13+
14+
var componentTypeMappings = {
15+
module: 'module',
16+
controller: 'controller',
17+
directive: 'directive',
18+
filter: 'filter',
19+
service: 'service',
20+
factory: 'service',
21+
provider: 'service',
22+
value: 'service',
23+
constant: 'constant'
24+
};
25+
26+
var filenameUtil = {
27+
firstToUpper: function (value) {
28+
return value[0].toUpperCase() + value.slice(1);
29+
},
30+
removeTypeSuffix: function (name, type) {
31+
var nameTypeLengthDiff = name.length - type.length;
32+
if (nameTypeLengthDiff <= 0) {
33+
return name;
34+
}
35+
var typeCamelCase = this.firstToUpper(type);
36+
if (name.indexOf(typeCamelCase) === nameTypeLengthDiff) {
37+
return name.slice(0, nameTypeLengthDiff);
38+
} else {
39+
return name;
40+
}
41+
},
42+
transformComponentName: function (name, options) {
43+
var nameStyle = options.nameStyle;
44+
var nameSeparator = separators[nameStyle];
45+
if (nameSeparator) {
46+
var replacement = "$1" + nameSeparator + "$2";
47+
name = name.replace(/([a-z])([A-Z])/g, replacement).toLowerCase();
48+
}
49+
return name;
50+
},
51+
createExpectedName: function (name, type, options) {
52+
var typeSeparator = separators[options.typeSeparator];
53+
54+
if (options.ignoreTypeSuffix) {
55+
name = filenameUtil.removeTypeSuffix(name, type);
56+
}
57+
if (options.nameStyle) {
58+
name = filenameUtil.transformComponentName(name, options);
59+
}
60+
if (typeSeparator !== undefined) {
61+
name = name + typeSeparator + type;
62+
}
63+
return name + fileEnding;
64+
65+
}
66+
};
67+
68+
return function (context) {
69+
var options = context.options[0] || {},
70+
filename = path.basename(context.getFilename());
71+
72+
return {
73+
74+
'CallExpression': function (node) {
75+
76+
if (utils.isAngularComponent(node) && utils.isMemberExpression(node.callee)) {
77+
var name = node.arguments[0].value,
78+
type = componentTypeMappings[node.callee.property.name],
79+
expectedName;
80+
81+
if (type === undefined|| (type === 'service' && node.callee.object.name === '$provide')) {
82+
return;
83+
}
84+
85+
expectedName = filenameUtil.createExpectedName(name, type, options);
86+
87+
if (expectedName !== filename) {
88+
context.report(node, 'Filename must be "{{expectedName}}"', {
89+
expectedName: expectedName
90+
});
91+
}
92+
}
93+
}
94+
};
95+
}
96+
}());

rules/utils/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
},
9696

9797
isAngularModuleDeclaration: function(node){
98-
return this.isAngularComponent(node) && node.callee !== undefined && node.callee.type === 'MemberExpression' && node.callee.property.name === 'module'
98+
return this.isAngularComponent(node) && this.isMemberExpression(node.callee) && node.callee.property.name === 'module'
9999
},
100100

101101
isAngularModuleGetter: function(node){

test/ng_file_name.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
var rule = require('../rules/ng_file_name'),
6+
RuleTester = require("eslint").RuleTester;
7+
8+
//------------------------------------------------------------------------------
9+
// Tests
10+
//------------------------------------------------------------------------------
11+
12+
var eslintTester = new RuleTester();
13+
eslintTester.run('ng_file_name', rule, {
14+
valid: [{
15+
// basic module
16+
filename: 'myModule.js',
17+
code: 'angular.module("myModule", []);'
18+
}, {
19+
// basic filter
20+
filename: 'someFilter.js',
21+
code: 'app.filter("someFilter", function(){});'
22+
}, {
23+
// basic controller
24+
filename: 'SomeController.js',
25+
code: 'app.controller("SomeController", function(){});'
26+
}, {
27+
// basic service
28+
filename: 'myUtils.js',
29+
code: 'app.service("myUtils", function(){});'
30+
}, {
31+
// basic factory service
32+
filename: 'myUtils.js',
33+
code: 'app.factory("myUtils", function(){});'
34+
}, {
35+
// basic directive
36+
filename: 'beautifulDirective.js',
37+
code: 'app.directive("beautifulDirective", function(){});'
38+
}, {
39+
// typeSeparator dot with filter
40+
filename: 'src/app/myFilter.filter.js',
41+
code: 'app.filter("myFilter", function(){});',
42+
options: [{
43+
typeSeparator: 'dot'
44+
}]
45+
}, {
46+
// ignore $provide declarations
47+
filename: 'src/app/myApp.module.js',
48+
code: '$provide.value("accountsService", accountsService);'
49+
}, {
50+
// ignore test declarations
51+
filename: 'src/app/fooBar.spec.js',
52+
code: 'it("myApp", function(){})'
53+
}, {
54+
// ignore test declarations
55+
filename: 'src/app/myService.spec.js',
56+
code: '$httpBackend.expectGET("/api/my/service").respond(200, dummyVorversicherer)'
57+
}, {
58+
// typeSeparator dash with service (factory)
59+
filename: 'src/app/someUtil-service.js',
60+
code: 'app.factory("someUtil", function(){});',
61+
options: [{
62+
typeSeparator: 'dash'
63+
}]
64+
}, {
65+
// typeSeparator underscore with controller
66+
filename: 'src/app/SomeController_controller.js',
67+
code: 'app.controller("SomeController", function(){});',
68+
options: [{
69+
typeSeparator: 'underscore'
70+
}]
71+
}, {
72+
// typeSeparator dot with controller and ignored type suffix
73+
filename: 'src/app/Avengers.controller.js',
74+
code: 'app.controller("AvengersController", function(){});',
75+
options: [{
76+
typeSeparator: 'dot',
77+
ignoreTypeSuffix: true
78+
}]
79+
}, {
80+
// typeSeparator dot with controller and ignored type suffix
81+
filename: 'src/app/Avengers.controller.js',
82+
code: 'app.controller("AvengersController", function(){});',
83+
options: [{
84+
typeSeparator: 'dot',
85+
ignoreTypeSuffix: true
86+
}]
87+
}, {
88+
// typeSeparator dot with service and ignored type suffix
89+
filename: 'src/app/avengers.service.js',
90+
code: 'app.factory("avengersService", function(){});',
91+
options: [{
92+
typeSeparator: 'dot',
93+
ignoreTypeSuffix: true
94+
}]
95+
}, {
96+
// typeSeparator dot with service and ignored type suffix
97+
filename: 'src/app/avengersApi.service.js',
98+
code: 'app.factory("avengersApi", function(){});',
99+
options: [{
100+
typeSeparator: 'dot',
101+
ignoreTypeSuffix: true
102+
}]
103+
}, {
104+
// typeSeparator dot with service and ignored type suffix (optimization: name shorter than type name)
105+
filename: 'src/app/utils.service.js',
106+
code: 'app.factory("utils", function(){});',
107+
options: [{
108+
typeSeparator: 'dot',
109+
ignoreTypeSuffix: true
110+
}]
111+
}, {
112+
// nameStyle dash and typeSeparator dash with service
113+
filename: 'src/app/app-utils-service.js',
114+
code: 'app.factory("appUtils", function(){});',
115+
options: [{
116+
typeSeparator: 'dash',
117+
nameStyle: 'dash'
118+
}]
119+
}, {
120+
// nameStyle underscore and typeSeparator dot with directive
121+
filename: 'src/app/my_tab.directive.js',
122+
code: 'app.directive("myTab", function(){});',
123+
options: [{
124+
typeSeparator: 'dot',
125+
nameStyle: 'underscore'
126+
}]
127+
}],
128+
invalid: [{
129+
filename: 'src/app/filters.js',
130+
code: 'app.filter("myFilter", function(){});',
131+
errors: [{ message: 'Filename must be "myFilter.js"'}]
132+
},{
133+
filename: 'src/app/myFilter.js',
134+
code: 'app.filter("myFilter", function(){});',
135+
options: [{
136+
typeSeparator: 'dot'
137+
}],
138+
errors: [{ message: 'Filename must be "myFilter.filter.js"'}]
139+
},{
140+
// typeSeparator underscore with service
141+
filename: 'src/someService_controller.js',
142+
code: 'app.factory("someService", function(){});',
143+
options: [{
144+
typeSeparator: 'underscore'
145+
}],
146+
errors: [{ message: 'Filename must be "someService_service.js"'}]
147+
}, {
148+
// typeSeparator dot with controller, but no ignored type suffix
149+
filename: 'src/app/Avengers.controller.js',
150+
code: 'app.controller("AvengersController", function(){});',
151+
options: [{
152+
typeSeparator: 'dot'
153+
}],
154+
errors: [{ message: 'Filename must be "AvengersController.controller.js"'}]
155+
}, {
156+
// typeSeparator dot with controller and ignored type suffix
157+
filename: 'src/app/AvengersController.controller.js',
158+
code: 'app.controller("AvengersController", function(){});',
159+
options: [{
160+
typeSeparator: 'dot',
161+
ignoreTypeSuffix: true
162+
}],
163+
errors: [{ message: 'Filename must be "Avengers.controller.js"'}]
164+
}, {
165+
// nameStyle dash and typeSeparator dot with directive
166+
filename: 'src/app/avangerProfile.directive.js',
167+
code: 'app.directive("avangerProfile", function(){});',
168+
options: [{
169+
typeSeparator: 'dot',
170+
nameStyle: 'dash'
171+
}],
172+
errors: [{ message: 'Filename must be "avanger-profile.directive.js"'}]
173+
}]
174+
});

0 commit comments

Comments
 (0)