eslint-codemod-utils
Version:
A collection of AST helper functions for more complex ESLint rule fixes.
344 lines (343 loc) • 9.76 kB
JavaScript
;
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;