member-delimiter-style.js 7.85 KB
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
const util = __importStar(require("../util"));
const definition = {
    type: 'object',
    properties: {
        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 invalid syntax
                delimiter: { enum: ['semi', 'comma'] },
                requireLast: { type: 'boolean' },
            },
            additionalProperties: false,
        },
    },
    additionalProperties: false,
};
exports.default = util.createRule({
    name: 'member-delimiter-style',
    meta: {
        type: 'suggestion',
        docs: {
            description: 'Require a specific member delimiter style for interfaces and type literals',
            category: 'Stylistic Issues',
            recommended: false,
        },
        fixable: 'code',
        messages: {
            unexpectedComma: 'Unexpected separator (,).',
            unexpectedSemi: 'Unexpected separator (;).',
            expectedComma: 'Expected a comma.',
            expectedSemi: 'Expected a semicolon.',
        },
        schema: [
            {
                type: 'object',
                properties: Object.assign({}, definition.properties, {
                    overrides: {
                        type: 'object',
                        properties: {
                            interface: definition,
                            typeLiteral: definition,
                        },
                        additionalProperties: false,
                    },
                }),
                additionalProperties: false,
            },
        ],
    },
    defaultOptions: [
        {
            multiline: {
                delimiter: 'semi',
                requireLast: true,
            },
            singleline: {
                delimiter: 'semi',
                requireLast: false,
            },
        },
    ],
    create(context, [options]) {
        var _a;
        const sourceCode = context.getSourceCode();
        // use the base options as the defaults for the cases
        const baseOptions = options;
        const overrides = (_a = baseOptions.overrides) !== null && _a !== void 0 ? _a : {};
        const interfaceOptions = util.deepMerge(baseOptions, overrides.interface);
        const typeLiteralOptions = util.deepMerge(baseOptions, overrides.typeLiteral);
        /**
         * Check the last token in the given member.
         * @param member the member to be evaluated.
         * @param opts the options to be validated.
         * @param isLast a flag indicating `member` is the last in the interface or type literal.
         */
        function checkLastToken(member, opts, isLast) {
            /**
             * Resolves the boolean value for the given setting enum value
             * @param type the option name
             */
            function getOption(type) {
                if (isLast && !opts.requireLast) {
                    // only turn the option on if its expecting no delimiter for the last member
                    return type === 'none';
                }
                return opts.delimiter === type;
            }
            let messageId = null;
            let missingDelimiter = false;
            const lastToken = sourceCode.getLastToken(member, {
                includeComments: false,
            });
            if (!lastToken) {
                return;
            }
            const optsSemi = getOption('semi');
            const optsComma = getOption('comma');
            const optsNone = getOption('none');
            if (lastToken.value === ';') {
                if (optsComma) {
                    messageId = 'expectedComma';
                }
                else if (optsNone) {
                    missingDelimiter = true;
                    messageId = 'unexpectedSemi';
                }
            }
            else if (lastToken.value === ',') {
                if (optsSemi) {
                    messageId = 'expectedSemi';
                }
                else if (optsNone) {
                    missingDelimiter = true;
                    messageId = 'unexpectedComma';
                }
            }
            else {
                if (optsSemi) {
                    missingDelimiter = true;
                    messageId = 'expectedSemi';
                }
                else if (optsComma) {
                    missingDelimiter = true;
                    messageId = 'expectedComma';
                }
            }
            if (messageId) {
                context.report({
                    node: lastToken,
                    loc: {
                        start: {
                            line: lastToken.loc.end.line,
                            column: lastToken.loc.end.column,
                        },
                        end: {
                            line: lastToken.loc.end.line,
                            column: lastToken.loc.end.column,
                        },
                    },
                    messageId,
                    fix(fixer) {
                        if (optsNone) {
                            // remove the unneeded token
                            return fixer.remove(lastToken);
                        }
                        const token = optsSemi ? ';' : ',';
                        if (missingDelimiter) {
                            // add the missing delimiter
                            return fixer.insertTextAfter(lastToken, token);
                        }
                        // correct the current delimiter
                        return fixer.replaceText(lastToken, token);
                    },
                });
            }
        }
        /**
         * Check the member separator being used matches the delimiter.
         * @param {ASTNode} node the node to be evaluated.
         */
        function checkMemberSeparatorStyle(node) {
            const isSingleLine = node.loc.start.line === node.loc.end.line;
            const members = node.type === experimental_utils_1.AST_NODE_TYPES.TSInterfaceBody ? node.body : node.members;
            const typeOpts = node.type === experimental_utils_1.AST_NODE_TYPES.TSInterfaceBody
                ? interfaceOptions
                : typeLiteralOptions;
            const opts = isSingleLine ? typeOpts.singleline : typeOpts.multiline;
            members.forEach((member, index) => {
                checkLastToken(member, opts !== null && opts !== void 0 ? opts : {}, index === members.length - 1);
            });
        }
        return {
            TSInterfaceBody: checkMemberSeparatorStyle,
            TSTypeLiteral: checkMemberSeparatorStyle,
        };
    },
});
//# sourceMappingURL=member-delimiter-style.js.map