Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

[FEAT][BREAKING][2/2][member-delimiter-style] Separate single/multiline config #206

Merged
merged 9 commits into from
Dec 11, 2018
75 changes: 42 additions & 33 deletions lib/rules/member-delimiter-style.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,32 @@
*/
"use strict";

const { deepMerge } = require("../util");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

const definition = {
type: "object",
properties: {
delimiter: { enum: ["none", "semi", "comma"] },
requireLast: { type: "boolean" },
singleLine: { enum: ["none", "semi", "comma"] },
multiline: {
type: "object",
properties: {
delimiter: { enum: ["none", "semi", "comma"] },
requireLast: { type: "boolean" },
},
additionalProperties: false,
},
singleline: {
type: "object",
properties: {
// note can't have "none" for single line delimiter as it's invlaid syntax
delimiter: { enum: ["semi", "comma"] },
requireLast: { type: "boolean" },
},
additionalProperties: false,
},
},
additionalProperties: false,
};
Expand All @@ -26,7 +42,7 @@ module.exports = {
"Require a specific member delimiter style for interfaces and type literals",
category: "TypeScript",
url:
"https://github.com/nzakas/eslint-plugin-typescript/blob/master/docs/rules/member-delimiter-style.md",
"https://github.com/bradzacher/eslint-plugin-typescript/blob/master/docs/rules/member-delimiter-style.md",
},
fixable: "code",
messages: {
Expand Down Expand Up @@ -59,21 +75,20 @@ module.exports = {

const overrides = options.overrides || {};
const defaults = {
delimiter: "semi",
requireLast: true,
singleLine: "semi",
multiline: {
delimiter: "semi",
requireLast: true,
},
singleline: {
delimiter: "semi",
requireLast: false,
},
};

const interfaceOptions = Object.assign(
{},
defaults,
options,
overrides.interface
);
const typeLiteralOptions = Object.assign(
{},
defaults,
options,
const baseOptions = deepMerge(defaults, options);
const interfaceOptions = deepMerge(baseOptions, overrides.interface);
const typeLiteralOptions = deepMerge(
baseOptions,
overrides.typeLiteral
);

Expand All @@ -87,12 +102,10 @@ module.exports = {
* @param {Object} opts the options to be validated.
* @param {boolean} isLast a flag indicating `member` is the last in the
* interface or type literal.
* @param {boolean} isSameLine a flag indicating the interface or type
* literal was declared in a single line.
* @returns {void}
* @private
*/
function checkLastToken(member, opts, isLast, isSameLine) {
function checkLastToken(member, opts, isLast) {
/**
* Resolves the boolean value for the given setting enum value
* @param {"semi" | "comma" | "none"} type the option name
Expand All @@ -103,11 +116,6 @@ module.exports = {
// only turn the option on if its expecting no delimiter for the last member
return type === "none";
}
if (isSameLine) {
// use single line config
return opts.singleLine === type;
}
// use normal config
return opts.delimiter === type;
}

Expand Down Expand Up @@ -187,18 +195,19 @@ module.exports = {
*/
function checkMemberSeparatorStyle(node) {
const isInterface = node.type === "TSInterfaceBody";

const isSingleLine = node.loc.start.line === node.loc.end.line;
const opts = isInterface ? interfaceOptions : typeLiteralOptions;

const members = isInterface ? node.body : node.members;

const typeOpts = isInterface
? interfaceOptions
: typeLiteralOptions;
const opts = isSingleLine
? typeOpts.singleline
: typeOpts.multiline;

members.forEach((member, index) => {
checkLastToken(
member,
opts,
index === members.length - 1,
isSingleLine
);
checkLastToken(member, opts, index === members.length - 1);
});
}

Expand Down
40 changes: 40 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,43 @@ exports.tslintRule = name => `\`${name}\` from TSLint`;
* @private
*/
exports.isTypescript = fileName => /\.tsx?$/.test(fileName);

/**
* Pure function - doesn't mutate either parameter!
* Merges two objects together deeply, overwriting the properties in first with the properties in second
* @template TFirst,TSecond
* @param {TFirst} first The first object
* @param {TSecond} second The second object
* @returns {Record<string, any>} a new object
*/
function deepMerge(first = {}, second = {}) {
// get the unique set of keys across both objects
const keys = new Set(Object.keys(first).concat(Object.keys(second)));

return Array.from(keys).reduce((acc, key) => {
const firstHasKey = key in first;
const secondHasKey = key in second;

if (firstHasKey && secondHasKey) {
if (
typeof first[key] === "object" &&
!Array.isArray(first[key]) &&
typeof second[key] === "object" &&
!Array.isArray(second[key])
) {
// object type
acc[key] = deepMerge(first[key], second[key]);
} else {
// value type
acc[key] = second[key];
}
} else if (firstHasKey) {
acc[key] = first[key];
} else {
acc[key] = second[key];
}

return acc;
}, {});
}
exports.deepMerge = deepMerge;
Loading