Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit 27e9d7e

Browse files
add prefer-object-literal rule (#79)
1 parent c7c7885 commit 27e9d7e

File tree

7 files changed

+308
-0
lines changed

7 files changed

+308
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
3232
* "switch" statements should have at least 3 "case" clauses ([`no-small-switch`])
3333
* "catch" clauses should do more than rethrow ([`no-useless-catch`])
3434
* Local variables should not be declared and then immediately returned or thrown ([`prefer-immediate-return`]) (:wrench: *fixable*)
35+
* Object literal syntax should be used ([`prefer-object-literal`])
3536
* Return of boolean expressions should not be wrapped into an "if-then-else" statement ([`prefer-single-boolean-return`])
3637
* A "while" loop should be used instead of a "for" loop ([`prefer-while`]) (:wrench: *fixable*)
3738

@@ -52,6 +53,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
5253
[`no-use-of-empty-return-value`]: ./docs/rules/no-use-of-empty-return-value.md
5354
[`no-useless-catch`]: ./docs/rules/no-useless-catch.md
5455
[`prefer-immediate-return`]: ./docs/rules/prefer-immediate-return.md
56+
[`prefer-object-literal`]: ./docs/rules/prefer-object-literal.md
5557
[`prefer-single-boolean-return`]: ./docs/rules/prefer-single-boolean-return.md
5658
[`prefer-while`]: ./docs/rules/prefer-while.md
5759

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# prefer-object-literal
2+
3+
Object literal syntax, which initializes an object's properties inside the object declaration is cleaner and clearer than the alternative: creating an empty object, and then giving it properties one by one.
4+
5+
An issue is raised when the following pattern is met:
6+
7+
* An empty object is created.
8+
* A consecutive single-line statement adds a property to the created object.
9+
10+
## Noncompliant Code Example
11+
12+
```javascript
13+
var person = {}; // Noncompliant
14+
person.firstName = "John";
15+
person.middleInitial = "Q";
16+
person.lastName = "Public";
17+
```
18+
19+
## Compliant Solution
20+
21+
```javascript
22+
var person = {
23+
firstName: "John",
24+
middleInitial: "Q",
25+
lastName: "Public",
26+
};
27+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
src/angular.js/i18n/src/converter.js: 38
2+
src/angular.js/src/ngAnimate/animateCss.js: 600
3+
src/brackets/src/extensibility/ExtensionManager.js: 313
4+
src/brackets/src/extensions/default/HealthData/HealthDataManager.js: 53
5+
src/brackets/src/extensions/default/InlineColorEditor/ColorEditor.js: 557,570,582
6+
src/brackets/src/extensions/default/JavaScriptRefactoring/RefactoringUtils.js: 365
7+
src/brackets/src/LiveDevelopment/LiveDevServerManager.js: 97
8+
src/Ghost/core/server/helpers/index.js: 1
9+
src/Ghost/core/server/helpers/navigation.js: 64
10+
src/Ghost/core/server/lib/image/image-size.js: 23
11+
src/Ghost/core/server/update-check.js: 87
12+
src/jest/packages/pretty-format/perf/test.js: 182
13+
src/jquery/external/sinon/sinon.js: 4500
14+
src/reveal.js/plugin/search/search.js: 165
15+
src/spectrum/api/models/user.js: 407
16+
src/three.js/editor/js/Command.js: 32
17+
src/three.js/editor/js/History.js: 164
18+
src/three.js/src/core/Geometry.js: 1286,1338
19+
src/three.js/src/core/Object3D.js: 649
20+
src/vue/packages/vue-server-renderer/basic.js: 3943
21+
src/vue/packages/vue-server-renderer/build.js: 3686
22+
src/vue/packages/vue-template-compiler/browser.js: 3058
23+
src/vue/packages/vue-template-compiler/build.js: 2660
24+
src/vue/packages/weex-template-compiler/build.js: 1459
25+
src/vue/packages/weex-vue-framework/factory.js: 3570,3572,5203
26+
src/vue/src/compiler/parser/index.js: 373
27+
src/vue/src/core/global-api/index.js: 22
28+
src/vue/src/core/instance/state.js: 314,316

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const sonarjsRules: [string, Linter.RuleLevel][] = [
3737
["no-use-of-empty-return-value", "error"],
3838
["no-useless-catch", "error"],
3939
["prefer-immediate-return", "error"],
40+
["prefer-object-literal", "error"],
4041
["prefer-single-boolean-return", "error"],
4142
["prefer-while", "error"],
4243
];

src/rules/prefer-object-literal.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
// https://jira.sonarsource.com/browse/RSPEC-2428
21+
22+
import { Rule, SourceCode } from "eslint";
23+
import { Node, Statement, Program, Identifier, BlockStatement, Expression } from "estree";
24+
import {
25+
isModuleDeclaration,
26+
isVariableDeclaration,
27+
isObjectExpression,
28+
isExpressionStatement,
29+
isAssignmentExpression,
30+
isMemberExpression,
31+
isIdentifier,
32+
} from "../utils/nodes";
33+
import { areEquivalent } from "../utils/equivalence";
34+
35+
const MESSAGE =
36+
"Declare one or more properties of this object inside of the object literal syntax instead of using separate statements.";
37+
38+
const rule: Rule.RuleModule = {
39+
create(context: Rule.RuleContext) {
40+
return {
41+
BlockStatement: (node: Node) => checkObjectInitialization((node as BlockStatement).body, context),
42+
Program: (node: Node) => {
43+
const statements = (node as Program).body.filter(
44+
(statement): statement is Statement => !isModuleDeclaration(statement),
45+
);
46+
checkObjectInitialization(statements, context);
47+
},
48+
};
49+
},
50+
};
51+
52+
function checkObjectInitialization(statements: Statement[], context: Rule.RuleContext) {
53+
let index = 0;
54+
while (index < statements.length - 1) {
55+
const objectDeclaration = getObjectDeclaration(statements[index]);
56+
if (objectDeclaration && isIdentifier(objectDeclaration.id)) {
57+
if (isPropertyAssignement(statements[index + 1], objectDeclaration.id, context.getSourceCode())) {
58+
context.report({ message: MESSAGE, node: objectDeclaration });
59+
}
60+
}
61+
index++;
62+
}
63+
}
64+
65+
function getObjectDeclaration(statement: Statement) {
66+
if (isVariableDeclaration(statement)) {
67+
return statement.declarations.find(declaration => !!declaration.init && isEmptyObjectExpression(declaration.init));
68+
}
69+
return undefined;
70+
}
71+
72+
function isEmptyObjectExpression(expression: Expression) {
73+
return isObjectExpression(expression) && expression.properties.length === 0;
74+
}
75+
76+
function isPropertyAssignement(statement: Statement, objectIdentifier: Identifier, sourceCode: SourceCode) {
77+
if (isExpressionStatement(statement) && isAssignmentExpression(statement.expression)) {
78+
const { left, right } = statement.expression;
79+
if (isMemberExpression(left)) {
80+
return (
81+
!left.computed &&
82+
isSingleLineExpression(right, sourceCode) &&
83+
areEquivalent(left.object, objectIdentifier, sourceCode)
84+
);
85+
}
86+
}
87+
return false;
88+
}
89+
90+
function isSingleLineExpression(expression: Expression, sourceCode: SourceCode) {
91+
const first = sourceCode.getFirstToken(expression)!.loc;
92+
const last = sourceCode.getLastToken(expression)!.loc;
93+
return first.start.line === last.end.line;
94+
}
95+
96+
export = rule;

src/utils/nodes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020
import { Rule } from "eslint";
2121
import * as estree from "estree";
2222

23+
const MODULE_DECLARATION_NODES = [
24+
"ImportDeclaration",
25+
"ExportNamedDeclaration",
26+
"ExportDefaultDeclaration",
27+
"ExportAllDeclaration",
28+
];
29+
2330
export function getParent(context: Rule.RuleContext) {
2431
const ancestors = context.getAncestors();
2532
return ancestors.length > 0 ? ancestors[ancestors.length - 1] : undefined;
@@ -89,6 +96,14 @@ export function isMemberExpression(node: estree.Node | undefined): node is estre
8996
return node !== undefined && node.type === "MemberExpression";
9097
}
9198

99+
export function isModuleDeclaration(node: estree.Node | undefined): node is estree.ModuleDeclaration {
100+
return node !== undefined && MODULE_DECLARATION_NODES.includes(node.type);
101+
}
102+
103+
export function isObjectExpression(node: estree.Node | undefined): node is estree.ObjectExpression {
104+
return node !== undefined && node.type === "ObjectExpression";
105+
}
106+
92107
export function isReturnStatement(node: estree.Node | undefined): node is estree.ReturnStatement {
93108
return node !== undefined && node.type === "ReturnStatement";
94109
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
import { RuleTester } from "eslint";
21+
22+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
23+
import rule = require("../../src/rules/prefer-object-literal");
24+
25+
ruleTester.run("prefer-literal", rule, {
26+
valid: [
27+
{
28+
code: `var x = {a: 2}`,
29+
},
30+
{
31+
code: `
32+
function Foo(a) {
33+
this.a = a;
34+
};
35+
var x = new Foo(2);`,
36+
},
37+
{
38+
code: `
39+
var x = {a: 2};
40+
y = "foo";`,
41+
},
42+
// FN
43+
{
44+
code: `
45+
var x;
46+
x = {};
47+
x.a = 2`,
48+
},
49+
// FN
50+
{
51+
code: `var x = {a: 2}; doSomething(); x.b = 3;`,
52+
},
53+
{
54+
code: `
55+
function foo() {
56+
var x = {a: 2};
57+
doSomething();
58+
}`,
59+
},
60+
{
61+
code: `var x = {}; x["a"] = 2;`,
62+
},
63+
// No issue on multiline expressions, may be done for readibility
64+
{
65+
code: `
66+
var x = {};
67+
x.foo = function () {
68+
doSomething();
69+
}
70+
var y = {};
71+
y.prop = {
72+
a: 1,
73+
b: 2
74+
}`,
75+
},
76+
// OK, report only when empty object
77+
{
78+
code: `var x = {a: 2}; x.b = 5;`,
79+
},
80+
],
81+
invalid: [
82+
{
83+
code: `var x = {}; x.a = 2;`,
84+
errors: [
85+
{
86+
message:
87+
"Declare one or more properties of this object inside of the object literal syntax instead of using separate statements.",
88+
line: 1,
89+
endLine: 1,
90+
column: 5,
91+
endColumn: 11,
92+
},
93+
],
94+
},
95+
{
96+
code: `
97+
var x = {},
98+
y = "hello";
99+
x.a = 2;`,
100+
errors: [
101+
{
102+
message:
103+
"Declare one or more properties of this object inside of the object literal syntax instead of using separate statements.",
104+
line: 2,
105+
endLine: 2,
106+
column: 13,
107+
endColumn: 19,
108+
},
109+
],
110+
},
111+
{
112+
code: `var x = {}; x.a = 2; x.b = 3`,
113+
errors: 1,
114+
},
115+
{
116+
code: `let x = {}; x.a = 2;`,
117+
errors: 1,
118+
},
119+
{
120+
code: `const x = {}; x.a = 2;`,
121+
errors: 1,
122+
},
123+
{
124+
code: `{ var x = {}; x.a = 2; }`,
125+
errors: 1,
126+
},
127+
{
128+
code: `if (a) { var x = {}; x.a = 2; }`,
129+
errors: 1,
130+
},
131+
{
132+
code: `function foo() {
133+
var x = {};
134+
x.a = 2;
135+
}`,
136+
errors: 1,
137+
},
138+
],
139+
});

0 commit comments

Comments
 (0)