Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 88 additions & 39 deletions codemods/pad-left/index.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,99 @@
import jscodeshift from 'jscodeshift';
import { removeImport } from '../shared.js';
import { ts } from '@ast-grep/napi';

/**
* @typedef {import('../../types.js').Codemod} Codemod
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
* @typedef {import('@ast-grep/napi').Edit} Edit
*/

/**
* @param {CodemodOptions} [options]
* @returns {Codemod}
*/
export default function (options) {
return {
name: 'pad-left',
to: 'native',
transform: ({ file }) => {
const j = jscodeshift;
const root = j(file.source);

const { identifier } = removeImport('pad-left', root, j);
root
.find(j.CallExpression, {
callee: {
type: 'Identifier',
name: identifier,
},
})
.replaceWith(({ node }) => {
const [stringArg, ...otherArgs] = node.arguments;
return j.callExpression(
j.memberExpression(
j.callExpression(
j.memberExpression(
// @ts-ignore
j.parenthesizedExpression(stringArg),
j.identifier('toString'),
),
[],
),
j.identifier('padStart'),
),
[...otherArgs],
);
});

return root.toSource(options);
},
};
}
return {
name: 'pad-left',
to: 'native',
transform: ({ file }) => {
const src = file.source;
const ast = ts.parse(src);
const root = ast.root();

const imports = root.findAll({
rule: {
any: [
{ pattern: { context: "import $NAME from 'pad-left'", strictness: 'relaxed' } },
{ pattern: { context: 'import $NAME from "pad-left"', strictness: 'relaxed' } },

{ pattern: { context: "const $NAME = require('pad-left')", strictness: 'relaxed' } },
{ pattern: { context: 'const $NAME = require("pad-left")', strictness: 'relaxed' } },

{ pattern: { context: "let $NAME = require('pad-left')", strictness: 'relaxed' } },
{ pattern: { context: 'let $NAME = require("pad-left")', strictness: 'relaxed' } },

{ pattern: { context: "var $NAME = require('pad-left')", strictness: 'relaxed' } },
{ pattern: { context: 'var $NAME = require("pad-left")', strictness: 'relaxed' } },
],
},
});

/** @type {Edit[]} */
const edits = [];
/** @type {string[]} */
const localNames = [];

for (const imp of imports) {
const name = imp.getMatch('NAME')?.text();
if (name && !localNames.includes(name)) {
localNames.push(name);
}

const rng = imp.range();
let start = rng.start.index;
let end = rng.end.index;

const next2 = src.slice(end, end + 2);
if (next2 === '\r\n') {
end += 2;
} else {
const next1 = src[end];
if (next1 === '\n' || next1 === '\r') end += 1;
}
Comment on lines +55 to +61
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this part. Maybe there's a better way to do this?


edits.push({ startPos: start, endPos: end, insertedText: '' });
}

if (localNames.length === 0) {
return edits.length ? root.commitEdits(edits) : file.source;
}

const usagePatterns = [];
for (const name of localNames) {
usagePatterns.push(
{ pattern: `${name}($VALUE, $WIDTH, $FILL)` },
{ pattern: `${name}($VALUE, $WIDTH)` },
{ pattern: `${name}($VALUE)` },
);
}

const calls = root.findAll({ rule: { any: usagePatterns } });

for (const call of calls) {
const value = call.getMatch('VALUE');
if (!value) continue;

const width = call.getMatch('WIDTH');
const fill = call.getMatch('FILL');

const args = [];
if (width) args.push(width.text());
if (fill) args.push(fill.text());

const newText = `(${value.text()}).toString().padStart(${args.join(', ')})`;
edits.push(call.replace(newText));
}

return edits.length ? root.commitEdits(edits) : file.source;
},
};
}