diff --git a/docs/rules/indent.md b/docs/rules/indent.md index 4719d5c03a1..282e8ab833f 100644 --- a/docs/rules/indent.md +++ b/docs/rules/indent.md @@ -74,6 +74,12 @@ This rule has an object option: * `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. * `"MemberExpression"` (off by default) enforces indentation level for multi-line property chains (except in variable declarations and assignments) +* `"FunctionDeclaration"` takes an object to define rules for function declarations. + * `parameters` (off by default) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. + * `body` (default: 1) enforces indentation level for the body of a function declaration. +* `"FunctionExpression"` takes an object to define rules for function expressions. + * `parameters` (off by default) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. + * `body` (default: 1) enforces indentation level for the body of a function expression. Level of indentation denotes the multiple of the indent specified. Example: @@ -281,6 +287,102 @@ var bip = aardvark.badger .coyote; ``` +### FunctionDeclaration + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ + +function foo(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ + +function foo(bar, baz, + qux, boop) { + qux(); +} +``` + +### FunctionExpression + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ + +var foo = function(bar, + baz, + qux) { + qux(); +} +``` + +Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + +Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: + +```js +/*eslint indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ + +var foo = function(bar, baz, + qux, boop) { + qux(); +} +``` + ## Compatibility * **JSHint**: `indent` diff --git a/lib/rules/indent.js b/lib/rules/indent.js index 792bd6fab3a..f723fbfca7e 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -73,6 +73,46 @@ module.exports = { MemberExpression: { type: "integer", minimum: 0 + }, + FunctionDeclaration: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + }, + body: { + type: "integer", + minimum: 0 + } + } + }, + FunctionExpression: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0 + }, + { + enum: ["first"] + } + ] + }, + body: { + type: "integer", + minimum: 0 + } + } } }, additionalProperties: false @@ -84,6 +124,8 @@ module.exports = { const MESSAGE = "Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}."; const DEFAULT_VARIABLE_INDENT = 1; + const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config + const DEFAULT_FUNCTION_BODY_INDENT = 1; let indentType = "space"; let indentSize = 4; @@ -94,7 +136,15 @@ module.exports = { let: DEFAULT_VARIABLE_INDENT, const: DEFAULT_VARIABLE_INDENT }, - outerIIFEBody: null + outerIIFEBody: null, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT + } }; const sourceCode = context.getSourceCode(); @@ -131,6 +181,14 @@ module.exports = { if (typeof opts.MemberExpression === "number") { options.MemberExpression = opts.MemberExpression; } + + if (typeof opts.FunctionDeclaration === "object") { + Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration); + } + + if (typeof opts.FunctionExpression === "object") { + Object.assign(options.FunctionExpression, opts.FunctionExpression); + } } } @@ -492,11 +550,15 @@ module.exports = { } // function body indent should be indent + indent size, unless this - // is the outer IIFE and that option is enabled. + // is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled. let functionOffset = indentSize; if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) { functionOffset = options.outerIIFEBody * indentSize; + } else if (calleeNode.type === "FunctionExpression") { + functionOffset = options.FunctionExpression.body * indentSize; + } else if (calleeNode.type === "FunctionDeclaration") { + functionOffset = options.FunctionDeclaration.body * indentSize; } indent += functionOffset; @@ -884,6 +946,28 @@ module.exports = { const caseIndent = expectedCaseIndent(node); checkNodesIndent(node.consequent, caseIndent + indentSize); + }, + + FunctionDeclaration(node) { + if (isSingleLineNode(node)) { + return; + } + if (options.FunctionDeclaration.parameters === "first" && node.params.length) { + checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); + } else if (options.FunctionDeclaration.parameters !== null) { + checkNodesIndent(node.params, indentSize * options.FunctionDeclaration.parameters); + } + }, + + FunctionExpression(node) { + if (isSingleLineNode(node)) { + return; + } + if (options.FunctionExpression.parameters === "first" && node.params.length) { + checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); + } else if (options.FunctionExpression.parameters !== null) { + checkNodesIndent(node.params, indentSize * options.FunctionExpression.parameters); + } } }; diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 50fb0f0f07b..28950e5b39a 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -1406,6 +1406,94 @@ ruleTester.run("indent", rule, { " qux();\n" + "}", options: [2] + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: 1, body: 2}}] + }, + { + code: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: 3, body: 1}}] + }, + { + code: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [4, {FunctionDeclaration: {parameters: 1, body: 3}}] + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: "first", body: 1}}] + }, + { + code: + "function foo(aaa, bbb)\n" + + "{\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {body: 3}}] // FIXME: what is the default for `parameters`? + }, + { + code: + "function foo(\n" + + " aaa,\n" + + " bbb) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: "first", body: 2}}] // FIXME: make sure this is correct + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + "bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: 2, body: 0}}] + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: 1, body: 10}}] + }, + { + code: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + options: [4, {FunctionExpression: {parameters: "first", body: 1}}] + }, + { + code: + "var foo = function(\n" + + " aaa, bbb, ccc,\n" + + " ddd, eee) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: "first", body: 3}}] // FIXME: make sure this is correct } ], invalid: [ @@ -2519,6 +2607,162 @@ ruleTester.run("indent", rule, { " }", options: [2], errors: expectedErrors([[3, 2, 4, "ExpressionStatement"], [4, 0, 5, "BlockStatement"]]) + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: 1, body: 2}}], + errors: expectedErrors([[2, 2, 4, "Identifier"], [3, 4, 6, "ExpressionStatement"]]) + }, + { + code: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + "bar();\n" + + "}", + output: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: 3, body: 1}}], + errors: expectedErrors([[2, 6, 2, "Identifier"], [3, 2, 0, "ExpressionStatement"]]) + }, + { + code: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [4, {FunctionDeclaration: {parameters: 1, body: 3}}], + errors: expectedErrors([[2, 4, 8, "Identifier"], [3, 4, 2, "Identifier"], [4, 12, 6, "ExpressionStatement"]]) + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: "first", body: 1}}], + errors: expectedErrors([[2, 13, 2, "Identifier"], [3, 13, 19, "Identifier"], [4, 2, 3, "ExpressionStatement"]]) + }, + { + code: + "function foo(aaa, bbb)\n" + + "{\n" + + "bar();\n" + + "}", + output: + "function foo(aaa, bbb)\n" + + "{\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {body: 3}}], + errors: expectedErrors([3, 6, 0, "ExpressionStatement"]) + }, + { + code: + "function foo(\n" + + "aaa,\n" + + " bbb) {\n" + + "bar();\n" + + "}", + output: + "function foo(\n" + + "aaa,\n" + + "bbb) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionDeclaration: {parameters: "first", body: 2}}], + errors: expectedErrors([[3, 0, 4, "Identifier"], [4, 4, 0, "ExpressionStatement"]]) + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + "bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: 2, body: 0}}], + errors: expectedErrors([[2, 4, 2, "Identifier"], [4, 4, 6, "Identifier"], [5, 0, 2, "ExpressionStatement"]]) + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: 1, body: 10}}], + errors: expectedErrors([[2, 2, 3, "Identifier"], [3, 2, 1, "Identifier"], [4, 20, 2, "ExpressionStatement"]]) + }, + { + code: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + options: [4, {FunctionExpression: {parameters: "first", body: 1}}], + errors: expectedErrors([[2, 19, 2, "Identifier"], [3, 19, 24, "Identifier"], [4, 4, 8, "ExpressionStatement"]]) + }, + { + code: + "var foo = function(\n" + + "aaa, bbb, ccc,\n" + + " ddd, eee) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(\n" + + "aaa, bbb, ccc,\n" + + "ddd, eee) {\n" + + " bar();\n" + + "}", + options: [2, {FunctionExpression: {parameters: "first", body: 3}}], + errors: expectedErrors([[3, 0, 4, "Identifier"], [4, 6, 2, "ExpressionStatement"]]) } ] });