text: const eslint = require('eslintbro')
  module.exports = function (config) {
    const linter = eslint.newLinter()
    linter.defineRule('object-shorthand-custom', rule);
    const messages = linter.verifyAndFix(config.code, {
      parser: 'typescript-eslint-parser',
      parserOptions: {
        'ecmaVersion': 6
      },
      rules: {
        'object-shorthand-custom': ["error", "always"] // or could be ["error", "never"]
      }
    });
    return {
      output: messages.output + `\n\n /*\n eslint output: \n${JSON.stringify(messages, null, 2)}}\n*/`
    };
  };
  
  
  /**
   * @fileoverview Rule to enforce concise object methods and properties.
   * @author Jamund Ferguson
   */
  
  "use strict";
  
  const OPTIONS = {
    always: "always",
    never: "never",
    methods: "methods",
    properties: "properties",
    consistent: "consistent",
    consistentAsNeeded: "consistent-as-needed"
  };
  
  //------------------------------------------------------------------------------
  // Requirements
  //------------------------------------------------------------------------------
  // const astUtils = require("../ast-utils");
  const astUtils = eslint.require('./ast-utils')
  
  //------------------------------------------------------------------------------
  // Rule Definition
  //------------------------------------------------------------------------------
  const rule = {
    meta: {
      docs: {
        description: "require or disallow method and property shorthand syntax for object literals",
        category: "ECMAScript 6",
        recommended: false,
        url: "https://eslint.org/docs/rules/object-shorthand"
      },
  
      fixable: "code",
  
      schema: {
        anyOf: [
          {
            type: "array",
            items: [
              {
                enum: ["always", "methods", "properties", "never", "consistent", "consistent-as-needed"]
              }
            ],
            minItems: 0,
            maxItems: 1
          },
          {
            type: "array",
            items: [
              {
                enum: ["always", "methods", "properties"]
              },
              {
                type: "object",
                properties: {
                  avoidQuotes: {
                    type: "boolean"
                  }
                },
                additionalProperties: false
              }
            ],
            minItems: 0,
            maxItems: 2
          },
          {
            type: "array",
            items: [
              {
                enum: ["always", "methods"]
              },
              {
                type: "object",
                properties: {
                  ignoreConstructors: {
                    type: "boolean"
                  },
                  avoidQuotes: {
                    type: "boolean"
                  },
                  avoidExplicitReturnArrows: {
                    type: "boolean"
                  }
                },
                additionalProperties: false
              }
            ],
            minItems: 0,
            maxItems: 2
          }
        ]
      }
    },
  
    create(context) {
      const APPLY = context.options[0] || OPTIONS.always;
      const APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always;
      const APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always;
      const APPLY_NEVER = APPLY === OPTIONS.never;
      const APPLY_CONSISTENT = APPLY === OPTIONS.consistent;
      const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded;
  
      const PARAMS = context.options[1] || {};
      const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors;
      const AVOID_QUOTES = PARAMS.avoidQuotes;
      const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows;
      const sourceCode = context.getSourceCode();
  
      //--------------------------------------------------------------------------
      // Helpers
      //--------------------------------------------------------------------------
  
      /**
       * Determines if the first character of the name is a capital letter.
       * @param {string} name The name of the node to evaluate.
       * @returns {boolean} True if the first character of the property name is a capital letter, false if not.
       * @private
       */
      function isConstructor(name) {
        const firstChar = name.charAt(0);
  
        return firstChar === firstChar.toUpperCase();
      }
  
      /**
       * Determines if the property can have a shorthand form.
       * @param {ASTNode} property Property AST node
       * @returns {boolean} True if the property can have a shorthand form
       * @private
       *
       */
      function canHaveShorthand(property) {
        return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty");
      }
  
      /**
       * Checks whether a node is a string literal.
       * @param   {ASTNode} node - Any AST node.
       * @returns {boolean} `true` if it is a string literal.
       */
      function isStringLiteral(node) {
        return node.type === "Literal" && typeof node.value === "string";
      }
  
      /**
       * Determines if the property is a shorthand or not.
       * @param {ASTNode} property Property AST node
       * @returns {boolean} True if the property is considered shorthand, false if not.
       * @private
       *
       */
      function isShorthand(property) {
  
        // property.method is true when `{a(){}}`.
        return (property.shorthand || property.method);
      }
  
      /**
       * Determines if the property's key and method or value are named equally.
       * @param {ASTNode} property Property AST node
       * @returns {boolean} True if the key and value are named equally, false if not.
       * @private
       *
       */
      function isRedundant(property) {
        const value = property.value;
  
        if (value.type === "FunctionExpression") {
          return !value.id; // Only anonymous should be shorthand method.
        }
        if (value.type === "Identifier") {
          return astUtils.getStaticPropertyName(property) === value.name;
        }
  
        return false;
      }
  
      /**
       * Ensures that an object's properties are consistently shorthand, or not shorthand at all.
       * @param   {ASTNode} node Property AST node
       * @param   {boolean} checkRedundancy Whether to check longform redundancy
       * @returns {void}
       *
       */
      function checkConsistency(node, checkRedundancy) {
  
        // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand.
        const properties = node.properties.filter(canHaveShorthand);
  
        // Do we still have properties left after filtering the getters and setters?
        if (properties.length > 0) {
          const shorthandProperties = properties.filter(isShorthand);
  
          /*
           * If we do not have an equal number of longform properties as
           * shorthand properties, we are using the annotations inconsistently
           */
          if (shorthandProperties.length !== properties.length) {
  
            // We have at least 1 shorthand property
            if (shorthandProperties.length > 0) {
              context.report({ node, message: "Unexpected mix of shorthand and non-shorthand properties." });
            } else if (checkRedundancy) {
  
              /*
               * If all properties of the object contain a method or value with a name matching it's key,
               * all the keys are redundant.
               */
              const canAlwaysUseShorthand = properties.every(isRedundant);
  
              if (canAlwaysUseShorthand) {
                context.report({ node, message: "Expected shorthand for all properties." });
              }
            }
          }
        }
      }
  
      /**
       * Fixes a FunctionExpression node by making it into a shorthand property.
       * @param {SourceCodeFixer} fixer The fixer object
       * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value
       * @returns {Object} A fix for this node
       */
      function makeFunctionShorthand(fixer, node) {
        const firstKeyToken = node.computed
          ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken)
          : sourceCode.getFirstToken(node.key);
        const lastKeyToken = node.computed
          ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken)
          : sourceCode.getLastToken(node.key);
        const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
        let keyPrefix = "";
  
        if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) {
          return null;
        }
  
        if (node.value.async) {
          keyPrefix += "async ";
        }
        if (node.value.generator) {
          keyPrefix += "*";
        }
  
        if (node.value.type === "FunctionExpression") {
          const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function");
          const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken;
  
          return fixer.replaceTextRange(
            [firstKeyToken.range[0], node.range[1]],
            keyPrefix + keyText + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1])
          );
        }
        const arrowToken = sourceCode.getTokens(node.value).find(token => token.value === "=>");
        const tokenBeforeArrow = sourceCode.getTokenBefore(arrowToken);
        const hasParensAroundParameters = tokenBeforeArrow.type === "Punctuator" && tokenBeforeArrow.value === ")";
        const oldParamText = sourceCode.text.slice(sourceCode.getFirstToken(node.value, node.value.async ? 1 : 0).range[0], tokenBeforeArrow.range[1]);
        const newParamText = hasParensAroundParameters ? oldParamText : `(${oldParamText})`;
  
        return fixer.replaceTextRange(
          [firstKeyToken.range[0], node.range[1]],
          keyPrefix + keyText + newParamText + sourceCode.text.slice(arrowToken.range[1], node.value.range[1])
        );
  
      }
  
      /**
       * Fixes a FunctionExpression node by making it into a longform property.
       * @param {SourceCodeFixer} fixer The fixer object
       * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value
       * @returns {Object} A fix for this node
       */
      function makeFunctionLongform(fixer, node) {
        const firstKeyToken = node.computed ? sourceCode.getTokens(node).find(token => token.value === "[") : sourceCode.getFirstToken(node.key);
        const lastKeyToken = node.computed ? sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]") : sourceCode.getLastToken(node.key);
        const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
        let functionHeader = "function";
  
        if (node.value.async) {
          functionHeader = `async ${functionHeader}`;
        }
        if (node.value.generator) {
          functionHeader = `${functionHeader}*`;
        }
  
        return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`);
      }
  
      /*
       * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`),
       * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is
       * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical
       * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered,
       * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited.
       * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them
       * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule,
       * because converting it into a method would change the value of one of the lexical identifiers.
       */
      const lexicalScopeStack = [];
      const arrowsWithLexicalIdentifiers = new WeakSet();
      const argumentsIdentifiers = new WeakSet();
  
      /**
       * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack.
       * Also, this marks all `arguments` identifiers so that they can be detected later.
       * @returns {void}
       */
      function enterFunction() {
        lexicalScopeStack.unshift(new Set());
        context.getScope().variables.filter(variable => variable.name === "arguments").forEach(variable => {
          variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier));
        });
      }
  
      /**
       * Exits a function. This pops the current set of arrow functions off the lexical scope stack.
       * @returns {void}
       */
      function exitFunction() {
        lexicalScopeStack.shift();
      }
  
      /**
       * Marks the current function as having a lexical keyword. This implies that all arrow functions
       * in the current lexical scope contain a reference to this lexical keyword.
       * @returns {void}
       */
      function reportLexicalIdentifier() {
        lexicalScopeStack[0].forEach(arrowFunction => arrowsWithLexicalIdentifiers.add(arrowFunction));
      }
  
      //--------------------------------------------------------------------------
      // Public
      //--------------------------------------------------------------------------
  
      return {
        Program: enterFunction,
        FunctionDeclaration: enterFunction,
        FunctionExpression: enterFunction,
        "Program:exit": exitFunction,
        "FunctionDeclaration:exit": exitFunction,
        "FunctionExpression:exit": exitFunction,
  
        ArrowFunctionExpression(node) {
          lexicalScopeStack[0].add(node);
        },
        "ArrowFunctionExpression:exit"(node) {
          lexicalScopeStack[0].delete(node);
        },
  
        ThisExpression: reportLexicalIdentifier,
        Super: reportLexicalIdentifier,
        MetaProperty(node) {
          if (node.meta.name === "new" && node.property.name === "target") {
            reportLexicalIdentifier();
          }
        },
        Identifier(node) {
          if (argumentsIdentifiers.has(node)) {
            reportLexicalIdentifier();
          }
        },
  
        ObjectExpression(node) {
          if (APPLY_CONSISTENT) {
            checkConsistency(node, false);
          } else if (APPLY_CONSISTENT_AS_NEEDED) {
            checkConsistency(node, true);
          }
        },
  
        "Property:exit"(node) {
          const isConciseProperty = node.method || node.shorthand;
  
          // Ignore destructuring assignment
          if (node.parent.type === "ObjectPattern") {
            return;
          }
  
          // getters and setters are ignored
          if (node.kind === "get" || node.kind === "set") {
            return;
          }
  
          // only computed methods can fail the following checks
          if (node.computed && node.value.type !== "FunctionExpression" && node.value.type !== "ArrowFunctionExpression") {
            return;
          }
  
          //--------------------------------------------------------------
          // Checks for property/method shorthand.
          if (isConciseProperty) {
            if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) {
              const message = APPLY_NEVER ? "Expected longform method syntax." : "Expected longform method syntax for string literal keys.";
  
              // { x() {} } should be written as { x: function() {} }
              context.report({
                node,
                message,
                fix: fixer => makeFunctionLongform(fixer, node)
              });
            } else if (APPLY_NEVER) {
  
              // { x } should be written as { x: x }
              context.report({
                node,
                message: "Expected longform property syntax.",
                fix: fixer => fixer.insertTextAfter(node.key, `: ${node.key.name}`)
              });
            }
          } else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) {
            if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) {
              return;
            }
            if (AVOID_QUOTES && isStringLiteral(node.key)) {
              return;
            }
  
            // {[x]: function(){}} should be written as {[x]() {}}
            if (node.value.type === "FunctionExpression" ||
              node.value.type === "ArrowFunctionExpression" &&
              node.value.body.type === "BlockStatement" &&
              AVOID_EXPLICIT_RETURN_ARROWS &&
              !arrowsWithLexicalIdentifiers.has(node.value)
            ) {
              context.report({
                node,
                message: "Expected method shorthand.",
                fix: fixer => makeFunctionShorthand(fixer, node)
              });
            }
          } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) {
  
            // {x: x} should be written as {x}
            context.report({
              node,
              message: "Expected property shorthand.",
              fix(fixer) {
                return fixer.replaceText(node, node.value.name);
              }
            });
          } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) {
            if (AVOID_QUOTES) {
              return;
            }
  
            // {"x": x} should be written as {x}
            context.report({
              node,
              message: "Expected property shorthand.",
              fix(fixer) {
                return fixer.replaceText(node, node.value.name);
              }
            });
          }
        }
      };
    }
  };
  