Skip to content

Commit

Permalink
Update: add named imports and exports for object-curly-newline (#9876)
Browse files Browse the repository at this point in the history
* Update: add ImportDeclaration and ExportDeclaration

* Chore: add tests for import/export declaration

* Docs: update docs for object-curly-newline

* Chore: update comment in object-curly-newline

* Chore: fix JSDoc error

* Chore: add sourceType module to RuleTester option

* Update: combine logic for finding closing brace

* Chore: extract function, needsLinebreaks

* Docs: revert remove of "and"

* Update: rewrite logic for node options

* Docs: Correct grammar for options

* Chore: Correct grammar in JSDoc
  • Loading branch information
nicholaschuayunzhi authored and ilyavolodin committed Feb 10, 2018
1 parent e8efdd0 commit 47ac478
Show file tree
Hide file tree
Showing 3 changed files with 462 additions and 22 deletions.
50 changes: 48 additions & 2 deletions docs/rules/object-curly-newline.md
Expand Up @@ -19,19 +19,23 @@ Or an object option:
* `"minProperties"` requires line breaks if the number of properties is at least the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true`
* `"consistent": true` requires that either both curly braces, or neither, directly enclose newlines. Note that enabling this option will also change the behavior of the `minProperties` option. (See `minProperties` above for more information)

You can specify different options for object literals and destructuring assignments:
You can specify different options for object literals, destructuring assignments, and named imports and exports:

```json
{
"object-curly-newline": ["error", {
"ObjectExpression": "always",
"ObjectPattern": { "multiline": true }
"ObjectPattern": { "multiline": true },
"ImportDeclaration": "never",
"ExportDeclaration": { "multiline": true, "minProperties": 3 }
}]
}
```

* `"ObjectExpression"` configuration for object literals
* `"ObjectPattern"` configuration for object patterns of destructuring assignments
* `"ImportDeclaration"` configuration for named imports
* `"ExportDeclaration"` configuration for named exports

### always

Expand Down Expand Up @@ -463,6 +467,48 @@ let {k = function() {
}} = obj;
```

### ImportDeclaration and ExportDeclaration

Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options:

```js
/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/
/*eslint-env es6*/

import {foo, bar} from 'foo-bar';
import {foo as f, bar} from 'foo-bar';
import {foo,
bar} from 'foo-bar';

export {
foo,
bar
};
export {
foo as f,
bar
} from 'foo-bar';
```

Examples of **correct** code for this rule with the `{ "ImportDeclaration": "always", "ExportDeclaration": "never" }` options:

```js
/*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/
/*eslint-env es6*/

import {
foo,
bar
} from 'foo-bar';
import {
foo as f,
bar
} from 'foo-bar';

export { foo, bar } from 'foo-bar';
export { foo as f, bar } from 'foo-bar';
```

## Compatibility

* **JSCS**: [requirePaddingNewLinesInObjects](http://jscs.info/rule/requirePaddingNewLinesInObjects) and [disallowPaddingNewLinesInObjects](http://jscs.info/rule/disallowPaddingNewLinesInObjects)
Expand Down
86 changes: 67 additions & 19 deletions lib/rules/object-curly-newline.js
Expand Up @@ -10,6 +10,7 @@
//------------------------------------------------------------------------------

const astUtils = require("../ast-utils");
const lodash = require("lodash");

//------------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -73,19 +74,57 @@ function normalizeOptionValue(value) {
* Normalizes a given option value.
*
* @param {string|Object|undefined} options - An option value to parse.
* @returns {{ObjectExpression: {multiline: boolean, minProperties: number}, ObjectPattern: {multiline: boolean, minProperties: number}}} Normalized option object.
* @returns {{
* ObjectExpression: {multiline: boolean, minProperties: number, consistent: boolean},
* ObjectPattern: {multiline: boolean, minProperties: number, consistent: boolean},
* ImportDeclaration: {multiline: boolean, minProperties: number, consistent: boolean},
* ExportNamedDeclaration : {multiline: boolean, minProperties: number, consistent: boolean}
* }} Normalized option object.
*/
function normalizeOptions(options) {
if (options && (options.ObjectExpression || options.ObjectPattern)) {
const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);

if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) {
return {
ObjectExpression: normalizeOptionValue(options.ObjectExpression),
ObjectPattern: normalizeOptionValue(options.ObjectPattern)
ObjectPattern: normalizeOptionValue(options.ObjectPattern),
ImportDeclaration: normalizeOptionValue(options.ImportDeclaration),
ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration)
};
}

const value = normalizeOptionValue(options);

return { ObjectExpression: value, ObjectPattern: value };
return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value };
}

/**
* Determines if ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration
* node needs to be checked for missing line breaks
*
* @param {ASTNode} node - Node under inspection
* @param {Object} options - option specific to node type
* @param {Token} first - First object property
* @param {Token} last - Last object property
* @returns {boolean} `true` if node needs to be checked for missing line breaks
*/
function areLineBreaksRequired(node, options, first, last) {
let objectProperties;

if (node.type === "ObjectExpression" || node.type === "ObjectPattern") {
objectProperties = node.properties;
} else {

// is ImportDeclaration or ExportNamedDeclaration
objectProperties = node.specifiers;
}

return objectProperties.length >= options.minProperties ||
(
options.multiline &&
objectProperties.length > 0 &&
first.loc.start.line !== last.loc.end.line
);
}

//------------------------------------------------------------------------------
Expand All @@ -109,7 +148,9 @@ module.exports = {
type: "object",
properties: {
ObjectExpression: OPTION_VALUE,
ObjectPattern: OPTION_VALUE
ObjectPattern: OPTION_VALUE,
ImportDeclaration: OPTION_VALUE,
ExportDeclaration: OPTION_VALUE
},
additionalProperties: false,
minProperties: 1
Expand All @@ -125,32 +166,37 @@ module.exports = {

/**
* Reports a given node if it violated this rule.
*
* @param {ASTNode} node - A node to check. This is an ObjectExpression node or an ObjectPattern node.
* @param {{multiline: boolean, minProperties: number}} options - An option object.
* @param {ASTNode} node - A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
* @param {{multiline: boolean, minProperties: number, consistent: boolean}} options - An option object.
* @returns {void}
*/
function check(node) {
const options = normalizedOptions[node.type];

if (
(node.type === "ImportDeclaration" &&
!node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) ||
(node.type === "ExportNamedDeclaration" &&
!node.specifiers.some(specifier => specifier.type === "ExportSpecifier"))
) {
return;
}

const openBrace = sourceCode.getFirstToken(node, token => token.value === "{");

let closeBrace;

if (node.typeAnnotation) {
closeBrace = sourceCode.getTokenBefore(node.typeAnnotation);
} else {
closeBrace = sourceCode.getLastToken(node);
closeBrace = sourceCode.getLastToken(node, token => token.value === "}");
}

let first = sourceCode.getTokenAfter(openBrace, { includeComments: true });
let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true });
const needsLinebreaks = (
node.properties.length >= options.minProperties ||
(
options.multiline &&
node.properties.length > 0 &&
first.loc.start.line !== last.loc.end.line
)
);

const needsLineBreaks = areLineBreaksRequired(node, options, first, last);

const hasCommentsFirstToken = astUtils.isCommentToken(first);
const hasCommentsLastToken = astUtils.isCommentToken(last);

Expand All @@ -165,7 +211,7 @@ module.exports = {
first = sourceCode.getTokenAfter(openBrace);
last = sourceCode.getTokenBefore(closeBrace);

if (needsLinebreaks) {
if (needsLineBreaks) {
if (astUtils.isTokenOnSameLine(openBrace, first)) {
context.report({
message: "Expected a line break after this opening brace.",
Expand Down Expand Up @@ -244,7 +290,9 @@ module.exports = {

return {
ObjectExpression: check,
ObjectPattern: check
ObjectPattern: check,
ImportDeclaration: check,
ExportNamedDeclaration: check
};
}
};

0 comments on commit 47ac478

Please sign in to comment.