CoolFace
Apppublic

Umama-at-Bluchip/Quick-UI

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
key-spacing.js688 linesDownload Raw Back to rules
1/**2 * @fileoverview Rule to specify spacing of object literal keys and values3 * @author Brandon Mills4 * @deprecated in ESLint v8.53.05 */6"use strict";7 8//------------------------------------------------------------------------------9// Requirements10//------------------------------------------------------------------------------11 12const astUtils = require("./utils/ast-utils");13const { getGraphemeCount } = require("../shared/string-utils");14 15/**16 * Checks whether a string contains a line terminator as defined in17 * http://www.ecma-international.org/ecma-262/5.1/#sec-7.318 * @param {string} str String to test.19 * @returns {boolean} True if str contains a line terminator.20 */21function containsLineTerminator(str) {22    return astUtils.LINEBREAK_MATCHER.test(str);23}24 25/**26 * Gets the last element of an array.27 * @param {Array} arr An array.28 * @returns {any} Last element of arr.29 */30function last(arr) {31    return arr[arr.length - 1];32}33 34/**35 * Checks whether a node is contained on a single line.36 * @param {ASTNode} node AST Node being evaluated.37 * @returns {boolean} True if the node is a single line.38 */39function isSingleLine(node) {40    return (node.loc.end.line === node.loc.start.line);41}42 43/**44 * Checks whether the properties on a single line.45 * @param {ASTNode[]} properties List of Property AST nodes.46 * @returns {boolean} True if all properties is on a single line.47 */48function isSingleLineProperties(properties) {49    const [firstProp] = properties,50        lastProp = last(properties);51 52    return firstProp.loc.start.line === lastProp.loc.end.line;53}54 55/**56 * Initializes a single option property from the configuration with defaults for undefined values57 * @param {Object} toOptions Object to be initialized58 * @param {Object} fromOptions Object to be initialized from59 * @returns {Object} The object with correctly initialized options and values60 */61function initOptionProperty(toOptions, fromOptions) {62    toOptions.mode = fromOptions.mode || "strict";63 64    // Set value of beforeColon65    if (typeof fromOptions.beforeColon !== "undefined") {66        toOptions.beforeColon = +fromOptions.beforeColon;67    } else {68        toOptions.beforeColon = 0;69    }70 71    // Set value of afterColon72    if (typeof fromOptions.afterColon !== "undefined") {73        toOptions.afterColon = +fromOptions.afterColon;74    } else {75        toOptions.afterColon = 1;76    }77 78    // Set align if exists79    if (typeof fromOptions.align !== "undefined") {80        if (typeof fromOptions.align === "object") {81            toOptions.align = fromOptions.align;82        } else { // "string"83            toOptions.align = {84                on: fromOptions.align,85                mode: toOptions.mode,86                beforeColon: toOptions.beforeColon,87                afterColon: toOptions.afterColon88            };89        }90    }91 92    return toOptions;93}94 95/**96 * Initializes all the option values (singleLine, multiLine and align) from the configuration with defaults for undefined values97 * @param {Object} toOptions Object to be initialized98 * @param {Object} fromOptions Object to be initialized from99 * @returns {Object} The object with correctly initialized options and values100 */101function initOptions(toOptions, fromOptions) {102    if (typeof fromOptions.align === "object") {103 104        // Initialize the alignment configuration105        toOptions.align = initOptionProperty({}, fromOptions.align);106        toOptions.align.on = fromOptions.align.on || "colon";107        toOptions.align.mode = fromOptions.align.mode || "strict";108 109        toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));110        toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));111 112    } else { // string or undefined113        toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions));114        toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions));115 116        // If alignment options are defined in multiLine, pull them out into the general align configuration117        if (toOptions.multiLine.align) {118            toOptions.align = {119                on: toOptions.multiLine.align.on,120                mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode,121                beforeColon: toOptions.multiLine.align.beforeColon,122                afterColon: toOptions.multiLine.align.afterColon123            };124        }125    }126 127    return toOptions;128}129 130//------------------------------------------------------------------------------131// Rule Definition132//------------------------------------------------------------------------------133 134/** @type {import('../shared/types').Rule} */135module.exports = {136    meta: {137        deprecated: true,138        replacedBy: [],139        type: "layout",140 141        docs: {142            description: "Enforce consistent spacing between keys and values in object literal properties",143            recommended: false,144            url: "https://eslint.org/docs/latest/rules/key-spacing"145        },146 147        fixable: "whitespace",148 149        schema: [{150            anyOf: [151                {152                    type: "object",153                    properties: {154                        align: {155                            anyOf: [156                                {157                                    enum: ["colon", "value"]158                                },159                                {160                                    type: "object",161                                    properties: {162                                        mode: {163                                            enum: ["strict", "minimum"]164                                        },165                                        on: {166                                            enum: ["colon", "value"]167                                        },168                                        beforeColon: {169                                            type: "boolean"170                                        },171                                        afterColon: {172                                            type: "boolean"173                                        }174                                    },175                                    additionalProperties: false176                                }177                            ]178                        },179                        mode: {180                            enum: ["strict", "minimum"]181                        },182                        beforeColon: {183                            type: "boolean"184                        },185                        afterColon: {186                            type: "boolean"187                        }188                    },189                    additionalProperties: false190                },191                {192                    type: "object",193                    properties: {194                        singleLine: {195                            type: "object",196                            properties: {197                                mode: {198                                    enum: ["strict", "minimum"]199                                },200                                beforeColon: {201                                    type: "boolean"202                                },203                                afterColon: {204                                    type: "boolean"205                                }206                            },207                            additionalProperties: false208                        },209                        multiLine: {210                            type: "object",211                            properties: {212                                align: {213                                    anyOf: [214                                        {215                                            enum: ["colon", "value"]216                                        },217                                        {218                                            type: "object",219                                            properties: {220                                                mode: {221                                                    enum: ["strict", "minimum"]222                                                },223                                                on: {224                                                    enum: ["colon", "value"]225                                                },226                                                beforeColon: {227                                                    type: "boolean"228                                                },229                                                afterColon: {230                                                    type: "boolean"231                                                }232                                            },233                                            additionalProperties: false234                                        }235                                    ]236                                },237                                mode: {238                                    enum: ["strict", "minimum"]239                                },240                                beforeColon: {241                                    type: "boolean"242                                },243                                afterColon: {244                                    type: "boolean"245                                }246                            },247                            additionalProperties: false248                        }249                    },250                    additionalProperties: false251                },252                {253                    type: "object",254                    properties: {255                        singleLine: {256                            type: "object",257                            properties: {258                                mode: {259                                    enum: ["strict", "minimum"]260                                },261                                beforeColon: {262                                    type: "boolean"263                                },264                                afterColon: {265                                    type: "boolean"266                                }267                            },268                            additionalProperties: false269                        },270                        multiLine: {271                            type: "object",272                            properties: {273                                mode: {274                                    enum: ["strict", "minimum"]275                                },276                                beforeColon: {277                                    type: "boolean"278                                },279                                afterColon: {280                                    type: "boolean"281                                }282                            },283                            additionalProperties: false284                        },285                        align: {286                            type: "object",287                            properties: {288                                mode: {289                                    enum: ["strict", "minimum"]290                                },291                                on: {292                                    enum: ["colon", "value"]293                                },294                                beforeColon: {295                                    type: "boolean"296                                },297                                afterColon: {298                                    type: "boolean"299                                }300                            },301                            additionalProperties: false302                        }303                    },304                    additionalProperties: false305                }306            ]307        }],308        messages: {309            extraKey: "Extra space after {{computed}}key '{{key}}'.",310            extraValue: "Extra space before value for {{computed}}key '{{key}}'.",311            missingKey: "Missing space after {{computed}}key '{{key}}'.",312            missingValue: "Missing space before value for {{computed}}key '{{key}}'."313        }314    },315 316    create(context) {317 318        /**319         * OPTIONS320         * "key-spacing": [2, {321         *     beforeColon: false,322         *     afterColon: true,323         *     align: "colon" // Optional, or "value"324         * }325         */326        const options = context.options[0] || {},327            ruleOptions = initOptions({}, options),328            multiLineOptions = ruleOptions.multiLine,329            singleLineOptions = ruleOptions.singleLine,330            alignmentOptions = ruleOptions.align || null;331 332        const sourceCode = context.sourceCode;333 334        /**335         * Determines if the given property is key-value property.336         * @param {ASTNode} property Property node to check.337         * @returns {boolean} Whether the property is a key-value property.338         */339        function isKeyValueProperty(property) {340            return !(341                (property.method ||342                property.shorthand ||343                property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement"344            );345        }346 347        /**348         * Starting from the given node (a property.key node here) looks forward349         * until it finds the colon punctuator and returns it.350         * @param {ASTNode} node The node to start looking from.351         * @returns {ASTNode} The colon punctuator.352         */353        function getNextColon(node) {354            return sourceCode.getTokenAfter(node, astUtils.isColonToken);355        }356 357        /**358         * Starting from the given node (a property.key node here) looks forward359         * until it finds the last token before a colon punctuator and returns it.360         * @param {ASTNode} node The node to start looking from.361         * @returns {ASTNode} The last token before a colon punctuator.362         */363        function getLastTokenBeforeColon(node) {364            const colonToken = getNextColon(node);365 366            return sourceCode.getTokenBefore(colonToken);367        }368 369        /**370         * Starting from the given node (a property.key node here) looks forward371         * until it finds the first token after a colon punctuator and returns it.372         * @param {ASTNode} node The node to start looking from.373         * @returns {ASTNode} The first token after a colon punctuator.374         */375        function getFirstTokenAfterColon(node) {376            const colonToken = getNextColon(node);377 378            return sourceCode.getTokenAfter(colonToken);379        }380 381        /**382         * Checks whether a property is a member of the property group it follows.383         * @param {ASTNode} lastMember The last Property known to be in the group.384         * @param {ASTNode} candidate The next Property that might be in the group.385         * @returns {boolean} True if the candidate property is part of the group.386         */387        function continuesPropertyGroup(lastMember, candidate) {388            const groupEndLine = lastMember.loc.start.line,389                candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line;390 391            if (candidateValueStartLine - groupEndLine <= 1) {392                return true;393            }394 395            /*396             * Check that the first comment is adjacent to the end of the group, the397             * last comment is adjacent to the candidate property, and that successive398             * comments are adjacent to each other.399             */400            const leadingComments = sourceCode.getCommentsBefore(candidate);401 402            if (403                leadingComments.length &&404                leadingComments[0].loc.start.line - groupEndLine <= 1 &&405                candidateValueStartLine - last(leadingComments).loc.end.line <= 1406            ) {407                for (let i = 1; i < leadingComments.length; i++) {408                    if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {409                        return false;410                    }411                }412                return true;413            }414 415            return false;416        }417 418        /**419         * Gets an object literal property's key as the identifier name or string value.420         * @param {ASTNode} property Property node whose key to retrieve.421         * @returns {string} The property's key.422         */423        function getKey(property) {424            const key = property.key;425 426            if (property.computed) {427                return sourceCode.getText().slice(key.range[0], key.range[1]);428            }429            return astUtils.getStaticPropertyName(property);430        }431 432        /**433         * Reports an appropriately-formatted error if spacing is incorrect on one434         * side of the colon.435         * @param {ASTNode} property Key-value pair in an object literal.436         * @param {string} side Side being verified - either "key" or "value".437         * @param {string} whitespace Actual whitespace string.438         * @param {int} expected Expected whitespace length.439         * @param {string} mode Value of the mode as "strict" or "minimum"440         * @returns {void}441         */442        function report(property, side, whitespace, expected, mode) {443            const diff = whitespace.length - expected;444 445            if ((446                diff && mode === "strict" ||447                diff < 0 && mode === "minimum" ||448                diff > 0 && !expected && mode === "minimum") &&449                !(expected && containsLineTerminator(whitespace))450            ) {451                const nextColon = getNextColon(property.key),452                    tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),453                    tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),454                    isKeySide = side === "key",455                    isExtra = diff > 0,456                    diffAbs = Math.abs(diff),457                    spaces = Array(diffAbs + 1).join(" ");458 459                const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;460                const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;461                const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;462                const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;463 464                let fix;465 466                if (isExtra) {467                    let range;468 469                    // Remove whitespace470                    if (isKeySide) {471                        range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];472                    } else {473                        range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];474                    }475                    fix = function(fixer) {476                        return fixer.removeRange(range);477                    };478                } else {479 480                    // Add whitespace481                    if (isKeySide) {482                        fix = function(fixer) {483                            return fixer.insertTextAfter(tokenBeforeColon, spaces);484                        };485                    } else {486                        fix = function(fixer) {487                            return fixer.insertTextBefore(tokenAfterColon, spaces);488                        };489                    }490                }491 492                let messageId = "";493 494                if (isExtra) {495                    messageId = side === "key" ? "extraKey" : "extraValue";496                } else {497                    messageId = side === "key" ? "missingKey" : "missingValue";498                }499 500                context.report({501                    node: property[side],502                    loc,503                    messageId,504                    data: {505                        computed: property.computed ? "computed " : "",506                        key: getKey(property)507                    },508                    fix509                });510            }511        }512 513        /**514         * Gets the number of characters in a key, including quotes around string515         * keys and braces around computed property keys.516         * @param {ASTNode} property Property of on object literal.517         * @returns {int} Width of the key.518         */519        function getKeyWidth(property) {520            const startToken = sourceCode.getFirstToken(property);521            const endToken = getLastTokenBeforeColon(property.key);522 523            return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1]));524        }525 526        /**527         * Gets the whitespace around the colon in an object literal property.528         * @param {ASTNode} property Property node from an object literal.529         * @returns {Object} Whitespace before and after the property's colon.530         */531        function getPropertyWhitespace(property) {532            const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice(533                property.key.range[1], property.value.range[0]534            ));535 536            if (whitespace) {537                return {538                    beforeColon: whitespace[1],539                    afterColon: whitespace[2]540                };541            }542            return null;543        }544 545        /**546         * Creates groups of properties.547         * @param {ASTNode} node ObjectExpression node being evaluated.548         * @returns {Array<ASTNode[]>} Groups of property AST node lists.549         */550        function createGroups(node) {551            if (node.properties.length === 1) {552                return [node.properties];553            }554 555            return node.properties.reduce((groups, property) => {556                const currentGroup = last(groups),557                    prev = last(currentGroup);558 559                if (!prev || continuesPropertyGroup(prev, property)) {560                    currentGroup.push(property);561                } else {562                    groups.push([property]);563                }564 565                return groups;566            }, [567                []568            ]);569        }570 571        /**572         * Verifies correct vertical alignment of a group of properties.573         * @param {ASTNode[]} properties List of Property AST nodes.574         * @returns {void}575         */576        function verifyGroupAlignment(properties) {577            const length = properties.length,578                widths = properties.map(getKeyWidth), // Width of keys, including quotes579                align = alignmentOptions.on; // "value" or "colon"580            let targetWidth = Math.max(...widths),581                beforeColon, afterColon, mode;582 583            if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration.584                beforeColon = alignmentOptions.beforeColon;585                afterColon = alignmentOptions.afterColon;586                mode = alignmentOptions.mode;587            } else {588                beforeColon = multiLineOptions.beforeColon;589                afterColon = multiLineOptions.afterColon;590                mode = alignmentOptions.mode;591            }592 593            // Conditionally include one space before or after colon594            targetWidth += (align === "colon" ? beforeColon : afterColon);595 596            for (let i = 0; i < length; i++) {597                const property = properties[i];598                const whitespace = getPropertyWhitespace(property);599 600                if (whitespace) { // Object literal getters/setters lack a colon601                    const width = widths[i];602 603                    if (align === "value") {604                        report(property, "key", whitespace.beforeColon, beforeColon, mode);605                        report(property, "value", whitespace.afterColon, targetWidth - width, mode);606                    } else { // align = "colon"607                        report(property, "key", whitespace.beforeColon, targetWidth - width, mode);608                        report(property, "value", whitespace.afterColon, afterColon, mode);609                    }610                }611            }612        }613 614        /**615         * Verifies spacing of property conforms to specified options.616         * @param {ASTNode} node Property node being evaluated.617         * @param {Object} lineOptions Configured singleLine or multiLine options618         * @returns {void}619         */620        function verifySpacing(node, lineOptions) {621            const actual = getPropertyWhitespace(node);622 623            if (actual) { // Object literal getters/setters lack colons624                report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode);625                report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode);626            }627        }628 629        /**630         * Verifies spacing of each property in a list.631         * @param {ASTNode[]} properties List of Property AST nodes.632         * @param {Object} lineOptions Configured singleLine or multiLine options633         * @returns {void}634         */635        function verifyListSpacing(properties, lineOptions) {636            const length = properties.length;637 638            for (let i = 0; i < length; i++) {639                verifySpacing(properties[i], lineOptions);640            }641        }642 643        /**644         * Verifies vertical alignment, taking into account groups of properties.645         * @param {ASTNode} node ObjectExpression node being evaluated.646         * @returns {void}647         */648        function verifyAlignment(node) {649            createGroups(node).forEach(group => {650                const properties = group.filter(isKeyValueProperty);651 652                if (properties.length > 0 && isSingleLineProperties(properties)) {653                    verifyListSpacing(properties, multiLineOptions);654                } else {655                    verifyGroupAlignment(properties);656                }657            });658        }659 660        //--------------------------------------------------------------------------661        // Public API662        //--------------------------------------------------------------------------663 664        if (alignmentOptions) { // Verify vertical alignment665 666            return {667                ObjectExpression(node) {668                    if (isSingleLine(node)) {669                        verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions);670                    } else {671                        verifyAlignment(node);672                    }673                }674            };675 676        }677 678        // Obey beforeColon and afterColon in each property as configured679        return {680            Property(node) {681                verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions);682            }683        };684 685 686    }687};688