UNPKG

eslint-codemod-utils

Version:

A collection of AST helper functions for more complex ESLint rule fixes.

344 lines (343 loc) 9.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.jsxAttribute = exports.jsxExpressionContainer = exports.jsxEmptyExpression = exports.jsxText = exports.jsxClosingElement = exports.jsxOpeningElement = exports.jsxSpreadAttribute = exports.jsxElement = exports.jsxMemberExpression = exports.jsxSpreadChild = exports.jsxFragment = exports.jsxClosingFragment = exports.jsxOpeningFragment = exports.jsxIdentifier = exports.comment = exports.comments = exports.whiteSpace = void 0; const types_1 = require("@typescript-eslint/types"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); const node_1 = require("./utils/node"); const whiteSpace = (loc) => { var _a; return ''.padStart(((_a = loc === null || loc === void 0 ? void 0 : loc.start) === null || _a === void 0 ? void 0 : _a.column) || 0, ' '); }; exports.whiteSpace = whiteSpace; const comments = (comments = []) => ({ comments, toString: () => comments.length ? `${comments.map(exports.comment).join('\n')}\n` : '', }); exports.comments = comments; const comment = ({ value, type, loc, ...other }) => ({ ...other, value, type, toString: () => // eslint-disable-next-line @typescript-eslint/no-non-null-assertion (0, exports.whiteSpace)(loc) + (type === types_1.AST_TOKEN_TYPES.Line ? `// ${value}` : `/* ${value} */`), }); exports.comment = comment; /** * __JSXIdentifier__ * * @param param Takes a string or the shape of a {ESTree.JSXIdentifier} node * @returns {ESTree.JSXIdentifier} node */ const jsxIdentifier = (param) => { const name = typeof param === 'string' ? param : param.name; const other = typeof param === 'object' ? param : {}; return { ...other, name, type: types_1.AST_NODE_TYPES.JSXIdentifier, toString: () => name, }; }; exports.jsxIdentifier = jsxIdentifier; /** * __JSXOpeningFragment__ * * @example * ```ts * <>hello</> * ^^ * ``` */ const jsxOpeningFragment = ({ ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.JSXOpeningFragment, toString: () => `<>`, }; }; exports.jsxOpeningFragment = jsxOpeningFragment; /** * __JSXClosingFragment__ * * @example * ```ts * <>hello</> * ^^ * ``` */ const jsxClosingFragment = ({ ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.JSXClosingFragment, toString: () => `</>`, }; }; exports.jsxClosingFragment = jsxClosingFragment; /** * __JSXFragment__ * * @example * ```ts * <>hello</> * ^^^^^^^^^^ * ``` */ const jsxFragment = ({ openingFragment, closingFragment, children = [], ...other }) => ({ ...other, openingFragment, closingFragment, children, type: types_1.AST_NODE_TYPES.JSXFragment, toString: () => { return `${(0, node_1.node)(openingFragment)}${children .map(node_1.node) .map(String) .join('\n')}${(0, node_1.node)(closingFragment)}`; }, }); exports.jsxFragment = jsxFragment; /** * __JSXSpreadChild__ * * @example * ```ts * <>{...child}</> * ^^^^^^^^^^ * ``` */ const jsxSpreadChild = ({ expression, ...other }) => { return { ...other, expression, type: types_1.AST_NODE_TYPES.JSXSpreadChild, toString: () => `{...${(0, node_1.node)(expression)}}`, }; }; exports.jsxSpreadChild = jsxSpreadChild; const jsxMemberExpression = ({ object, property, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.JSXMemberExpression, object, property, toString: () => `${(0, utils_1.isNodeOfType)(object, types_1.AST_NODE_TYPES.JSXIdentifier) ? (0, exports.jsxIdentifier)(object) : (0, node_1.node)(object)}.${(0, exports.jsxIdentifier)(property)}`, }); exports.jsxMemberExpression = jsxMemberExpression; const DEFAULT_LOC = { start: { column: 0, line: 0, }, end: { column: 0, line: 0, }, }; /** * __JSXElement__ * * @example * * Usage * ``` * import { jsxElement, jsxOpeningElement, jsxClosingElement, identifier } from 'eslint-codemod-utils' * * const modalName = identifier({ name: 'Modal' }) * const modal = jsxElement({ * openingElement: jsxOpeningElement({ name: modalName, selfClosing: false }), * closingElement: jsxClosingElement({ name: modalName }), * }) * ``` * * @example * * Produces * ```js * <Modal></Modal> * ``` * * @returns {JSXElement} */ const jsxElement = ({ openingElement, closingElement = null, children = [], loc = DEFAULT_LOC, ...other }) => ({ ...other, openingElement, closingElement, children, loc, type: types_1.AST_NODE_TYPES.JSXElement, toString: () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const indent = (0, exports.whiteSpace)(loc); const spacing = constants_1.DEFAULT_WHITESPACE + indent; return `${(0, exports.jsxOpeningElement)(openingElement)}${children.length ? spacing + children.map(node_1.node).map(String).join(spacing) + '\n' : ''}${closingElement ? `${indent}${(0, exports.jsxClosingElement)(closingElement)}` : ''}`; }, }); exports.jsxElement = jsxElement; /** * __JSXSpreadAttribute__ * * @example Usage * * ```js * import { jsxSpreadAttribute, identifier } from 'eslint-codemod-utils' * * const spreadAttr = jsxSpreadAttribute({ * argument: identifier({ name: 'spread' }) * }) * ``` * @example * * ```js * // Produces a spread attribute * <div {...spread}> * ⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃⌃ * ``` * * @returns {ESTree.JSXSpreadAttribute} */ const jsxSpreadAttribute = ({ argument, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.JSXSpreadAttribute, argument, toString: () => `{...${(0, node_1.node)(argument)}}`, }); exports.jsxSpreadAttribute = jsxSpreadAttribute; /** * __JSXOpeningElement__ * * Note: `leadingComments` is a parser-added extension (ESLint attaches it to * the node during traversal). It's not part of `TSESTree.JSXOpeningElement`, * but the library has historically supported rendering it so that lint fixers * can preserve documentation comments on the opening tag. We accept it as an * optional extra input field and render it before the opening tag. */ const jsxOpeningElement = ({ name, attributes = [], selfClosing = false, leadingComments = [], ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.JSXOpeningElement, name, attributes, selfClosing, toString: () => `${(0, exports.comments)(leadingComments)}<${name.type === types_1.AST_NODE_TYPES.JSXIdentifier ? (0, exports.jsxIdentifier)(name) : name.type === types_1.AST_NODE_TYPES.JSXMemberExpression ? (0, exports.jsxMemberExpression)(name) : // namespaced name not yet implemented name}${attributes && attributes.length ? ' ' + attributes.map(node_1.node).map(String).join(' ') : ''}${selfClosing ? ' />' : '>'}`, }); exports.jsxOpeningElement = jsxOpeningElement; /** * __JSXClosingElement__ * * @example * * ```js * // The below jsx div is a closing element. * // A closing element is expected to match a valid opening element of the same name * </div> * ``` * * @returns {ESTree.JSXClosingElement} */ const jsxClosingElement = ({ name, ...other }) => { return { ...other, type: types_1.AST_NODE_TYPES.JSXClosingElement, name, toString: () => `</${(0, node_1.node)(name)}>`, }; }; exports.jsxClosingElement = jsxClosingElement; /** * __JSXText__ * * @example * * ```js * // In the below jsx, the string, "hello world" is considered JSXText. * // JSXText can be a any number, boolean, or string value. * <div>hello world</div> * ``` * * @returns {ESTree.JSXText} */ const jsxText = ({ value, raw, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.JSXText, value, raw, toString: () => value, }); exports.jsxText = jsxText; /** * __JSXEmptyExpression__ * * @example * * ```tsx * <SomeJSX attribute={} /> * ^^ * ``` * * @returns {ESTree.JSXEmptyExpression} */ const jsxEmptyExpression = (node) => { return { ...node, type: types_1.AST_NODE_TYPES.JSXEmptyExpression, toString: () => `{}`, }; }; exports.jsxEmptyExpression = jsxEmptyExpression; /** * __JSXExpressionContainer__ * * @example * * ```tsx * <SomeJSX attribute={someValue} /> * ^^^^^^^^^^^ * ``` * * @returns {ESTree.JSXExpressionContainer} */ const jsxExpressionContainer = ({ expression, ...other }) => ({ ...other, expression, type: types_1.AST_NODE_TYPES.JSXExpressionContainer, toString: () => { if ((0, utils_1.isNodeOfType)(expression, types_1.AST_NODE_TYPES.JSXEmptyExpression)) { return '{}'; } if ((0, utils_1.isNodeOfType)(expression, types_1.AST_NODE_TYPES.JSXExpressionContainer)) { return String((0, exports.jsxExpressionContainer)(expression)); } return `{${(0, node_1.node)(expression)}}`; }, }); exports.jsxExpressionContainer = jsxExpressionContainer; /** * __JSXAttribute__ * * @example * * ```js * // In the below jsx, `a`, `b` and `c` reflect different valid * // jsx attributes. There values can come in many forms. * <div a={10} b="string" c={object} /> * ``` * * @returns {JSXAttribute} */ const jsxAttribute = ({ name, value, ...other }) => ({ ...other, type: types_1.AST_NODE_TYPES.JSXAttribute, name, value, toString: () => `${name.name}${value ? `=${(0, node_1.node)(value)}` : ''}`, }); exports.jsxAttribute = jsxAttribute;