unplugin-auto-decimal
Version:
<h1 align="center" style="margin-top: -10px">AutoDecimal</h1> <div align="center" style="margin-bottom:10px"> <a href="https://www.npmjs.com/package/unplugin-auto-decimal"> <img src="https://img.shields.io/npm/v/unplugin-auto-decimal"/> </a> </div
1,241 lines (1,219 loc) • 41.8 kB
JavaScript
// src/core/unplugin.ts
import { createFilter } from "@rollup/pluginutils";
import { isPackageExists as isPackageExists2 } from "local-pkg";
import { createUnplugin } from "unplugin";
// src/core/constant.ts
var PREFIX = "ad";
var BASE_COMMENT = `${PREFIX}-ignore`;
var NEXT_COMMENT = `next-${BASE_COMMENT}`;
var FILE_COMMENT = `file-${BASE_COMMENT}`;
var BLOCK_COMMENT = `block-${BASE_COMMENT}`;
var PATCH_DECLARATION = "const __PATCH_DECLARATION__ = ";
var RETURN_DECLARATION_CODE = "###code###";
var RETURN_FUNCTION_NAME = "__RETURN_DECLARATION_FN__";
var RETURN_DECLARATION_PREFIX = `function ${RETURN_FUNCTION_NAME}() {`;
var RETURN_DECLARATION_FN = `${RETURN_DECLARATION_PREFIX}${RETURN_DECLARATION_CODE}}`;
var LITERALS = ["StringLiteral", "NullLiteral", "BooleanLiteral", "TemplateLiteral"];
var OPERATOR = {
"+": "plus",
"-": "minus",
"*": "times",
"/": "div",
"**": "pow"
};
var OPERATOR_KEYS = Object.keys(OPERATOR);
var REGEX_SUPPORTED_EXT = /\.([cm]?[jt]s)x?$/;
var REGEX_VUE = [/\.vue$/, /\.vue\?vue/, /\.vue\?v=/];
var REGEX_NODE_MODULES = /node_modules/;
var DECIMAL_PKG_NAME = "__Decimal";
var PKG_NAME = "decimal.js-light";
var DEFAULT_TO_DECIMAL_CONFIG = {
precision: 2,
p: 2,
roundingModes: "ROUND_HALF_UP",
rm: "ROUND_HALF_UP",
callMethod: "toNumber",
cm: "toNumber",
name: "toDecimal"
};
var DEFAULT_NEW_FUNCTION_CONFIG = {
injectWindow: void 0,
toDecimal: false
};
var DECIMAL_RM_LIGHT = Object.freeze({
ROUND_UP: 0,
ROUND_DOWN: 1,
ROUND_CEIL: 2,
ROUND_FLOOR: 3,
ROUND_HALF_UP: 4,
ROUND_HALF_DOWN: 5,
ROUND_HALF_EVEN: 6,
ROUND_HALF_CEIL: 7,
ROUND_HALF_FLOOR: 8
});
var DECIMAL_RM = Object.freeze({
...DECIMAL_RM_LIGHT,
EUCLID: 9
});
var BIG_RM = Object.freeze({
ROUND_DOWN: 0,
ROUND_HALF_UP: 1,
ROUND_HALF_DOWN: 2,
ROUND_UP: 3
});
// src/core/generate.ts
import { existsSync } from "fs";
import { mkdir, readFile, writeFile } from "fs/promises";
import { dirname } from "path";
async function generateDeclaration(options) {
const filePath = options.dts;
const toDecimal = typeof options.toDecimal === "boolean" ? DEFAULT_TO_DECIMAL_CONFIG : options.toDecimal;
const content = `/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-auto-decimal
type ToDecimal = import('unplugin-auto-decimal/types').ToDecimal
declare interface String {
${toDecimal.name}: ToDecimal
}
declare interface Number {
${toDecimal.name}: ToDecimal
}
`;
const originalContent = existsSync(filePath) ? await readFile(filePath, "utf-8") : "";
if (originalContent !== content) {
await writeDeclaration(filePath, content);
}
}
async function writeDeclaration(filePath, content) {
await mkdir(dirname(filePath), { recursive: true });
return await writeFile(filePath, content, "utf-8");
}
// src/core/options.ts
import { resolve } from "path";
import process from "process";
import { isPackageExists } from "local-pkg";
var rootPath = process.cwd();
var defaultOptions = {
supportString: false,
tailPatchZero: false,
package: "decimal.js-light",
toDecimal: false,
dts: isPackageExists("typescript"),
supportNewFunction: false,
decimalName: "__Decimal"
};
function resolveOptions(rawOptions) {
const options = Object.assign({}, defaultOptions, rawOptions);
options.dts = !options.dts ? false : resolve(rootPath, typeof options.dts === "string" ? options.dts : "auto-decimal.d.ts");
options.toDecimal = !options.toDecimal ? false : options.toDecimal === true ? { ...DEFAULT_TO_DECIMAL_CONFIG } : Object.assign({}, DEFAULT_TO_DECIMAL_CONFIG, options.toDecimal);
options.supportNewFunction = !options.supportNewFunction ? false : options.supportNewFunction === true ? { toDecimal: options.toDecimal } : {
...DEFAULT_NEW_FUNCTION_CONFIG,
toDecimal: options.toDecimal,
...options.supportNewFunction
};
return options;
}
function mergeToDecimalOptions(rawOptions, toDecimalOptions) {
if (typeof toDecimalOptions === "boolean") {
return rawOptions;
}
const precision = toDecimalOptions.precision ?? toDecimalOptions.p ?? rawOptions.precision;
const callMethod = toDecimalOptions.callMethod ?? toDecimalOptions.cm ?? rawOptions.callMethod;
const roundingModes = toDecimalOptions.roundingModes ?? toDecimalOptions.rm ?? rawOptions.roundingModes;
return Object.assign(rawOptions, {
precision,
callMethod,
roundingModes,
p: precision,
cm: callMethod,
rm: roundingModes
});
}
// src/core/transform.ts
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import { isObjectExpression as isObjectExpression3 } from "@babel/types";
import { NodeTypes } from "@vue/compiler-core";
import { parse as vueParse } from "@vue/compiler-sfc";
import { MagicStringAST } from "magic-string-ast";
// src/core/traverse/ast.ts
import { isJSXEmptyExpression as isJSXEmptyExpression2 } from "@babel/types";
// src/core/traverse/binary-expression.ts
import { isNumericLiteral as isNumericLiteral2 } from "@babel/types";
// src/core/utils.ts
import { isArrowFunctionExpression, isBinaryExpression, isCallExpression, isFunctionDeclaration, isFunctionExpression, isIdentifier, isImportDefaultSpecifier, isImportNamespaceSpecifier, isImportSpecifier, isLiteral, isMemberExpression, isNumericLiteral, isStringLiteral, isTemplateLiteral, isVariableDeclarator } from "@babel/types";
function getRoundingMode(mode, packageName) {
if (typeof mode === "number") {
return mode;
}
if (packageName === "big.js") {
return BIG_RM[mode];
}
if (packageName === "decimal.js") {
return DECIMAL_RM[mode];
}
return DECIMAL_RM_LIGHT[mode];
}
function getRootBinaryExprPath(path) {
let parentPath = path.parentPath;
let binaryPath = path;
let loop = true;
while (loop && parentPath) {
if (isBinaryExpression(parentPath.node)) {
binaryPath = parentPath;
parentPath = parentPath.parentPath;
} else {
loop = false;
}
}
return binaryPath;
}
function getScopeBinding(path, name) {
if (!path || !name)
return;
if (typeof name !== "string") {
name = getObjectIdentifierName(name);
}
const binding = path.scope.hasBinding(name);
if (!binding) {
if (!path.scope.path.parentPath) {
return path.scope.getBinding(name);
}
return getScopeBinding(path.scope.path.parentPath, name);
}
return path.scope.getBinding(name);
}
function getTargetPath(path, isTargetFunction) {
let loop = true;
let parentPath = path;
while (loop && parentPath) {
if (isTargetFunction(parentPath?.parent)) {
loop = false;
} else {
parentPath = parentPath.parentPath;
}
}
return parentPath?.parentPath;
}
function isIntegerValue(node, path, options) {
if (options.autoDecimalOptions.toDecimal) {
return false;
}
return isNumeric(node, path, options, true);
}
function isStringNode(node) {
return isStringLiteral(node) || isTemplateLiteral(node);
}
function isFunctionNode(node) {
return isArrowFunctionExpression(node) || isFunctionExpression(node) || isFunctionDeclaration(node);
}
function isImportNode(node) {
return isImportNamespaceSpecifier(node) || isImportDefaultSpecifier(node) || isImportSpecifier(node);
}
function getFunctionName(path) {
if (isFunctionDeclaration(path.node)) {
return path.node.id?.name;
}
if (isArrowFunctionExpression(path.node) || isFunctionExpression(path.node)) {
const node = path.parent;
if (isVariableDeclarator(node)) {
return node.id.name;
}
}
}
function getPkgName(options) {
if (options.fromNewFunction) {
const { supportNewFunction } = options.autoDecimalOptions;
if (typeof supportNewFunction !== "boolean" && supportNewFunction.injectWindow) {
return `window.${supportNewFunction.injectWindow}`;
}
}
return options.decimalPkgName;
}
function getNodeValue(node, path, options, isInteger) {
if (isFunctionNode(node) || isImportNode(node) || isCallExpression(node)) {
return;
}
if (isLiteral(node)) {
return getLiteralValue(node, path, options);
}
const ownerPath = options.ownerPath ?? path;
let parentPath = ownerPath;
let binding;
const name = isIdentifier(node) ? node.name : isMemberExpression(node) ? getObjectIdentifierName(node) : "";
while (!binding && parentPath) {
binding = getScopeBinding(parentPath, name);
parentPath = parentPath.parentPath;
}
if (!binding) {
return;
}
if (!isVariableDeclarator(binding.path.node)) {
return;
}
const { init } = binding.path.node;
if (isCallExpression(init)) {
return;
}
if (isLiteral(init)) {
return getLiteralValue(init, binding.path, options);
}
if (isIdentifier(init)) {
return getNodeValue(init, binding.path, options, isInteger);
}
}
function isNumeric(node, path, options, isInteger = false) {
const value = getNodeValue(node, path, options, isInteger);
if (typeof value === "undefined") {
return false;
}
const isNotNumber = Number.isNaN(Number(value));
if (isNotNumber) {
return false;
}
if (isInteger && options.autoDecimalOptions.supportString) {
return false;
}
return isInteger ? !value.toString().includes(".") : true;
}
function getObjectIdentifierName(node) {
if (isMemberExpression(node.object)) {
return getObjectIdentifierName(node.object);
}
if (isIdentifier(node.object)) {
return node.object.name;
}
return "";
}
function getLiteralValue(node, path, options, isInteger) {
if (isNumericLiteral(node)) {
return node.value;
}
if (!options.autoDecimalOptions.supportString) {
return;
}
if (isStringLiteral(node)) {
return node.value;
}
if (isTemplateLiteral(node)) {
const { quasis, expressions } = node;
if (!expressions.length) {
return quasis.map((item) => item.value.raw).join("");
}
const quasisCopy = quasis.slice(1, -1);
let index = 0;
const exprList = [];
expressions.forEach((expr) => {
if (quasisCopy.length) {
const quasisItem = quasisCopy[index];
const start = quasisItem.loc.start;
const exprStart = expr.loc.start;
if (start.line < exprStart.line || start.line === exprStart.line && start.column <= exprStart.column) {
exprList.push(quasisItem.value.raw);
index++;
}
}
exprList.push(getNodeValue(expr, path, options, isInteger));
});
if (index < quasisCopy.length - 1) {
const remainingQuasis = quasisCopy.slice(index);
remainingQuasis.forEach((item) => exprList.push(item.value.raw));
}
exprList.unshift(quasis[0].value.raw);
exprList.push(quasis[quasis.length - 1].value.raw);
return exprList.join("");
}
}
// src/core/traverse/comment.ts
import { isJSXElement, isJSXEmptyExpression, isJSXExpressionContainer, isJSXOpeningElement } from "@babel/types";
function getComments(path) {
const leadingComments = path.node.leadingComments ?? [];
const trailingComments = path.node.trailingComments ?? [];
const currentLineComments = trailingComments.filter((comment) => {
return comment.loc?.start.line === path.node.loc?.start.line && comment.value.includes(BASE_COMMENT);
});
return [...leadingComments, ...currentLineComments];
}
function blockComment(path) {
skipAutoDecimalComment(path, BLOCK_COMMENT);
}
function nextComment(path) {
skipAutoDecimalComment(path, NEXT_COMMENT);
}
function innerComment(path, igc = [NEXT_COMMENT, BLOCK_COMMENT]) {
skipAutoDecimalComment(path, igc, true);
}
function skipAutoDecimalComment(path, igc, isJSX = false) {
let comments;
let startLine = -1;
const rawPath = path;
if (isJSX) {
if (isJSXOpeningElement(path.node)) {
path = path.parentPath;
startLine = path.node.loc?.start.line ?? -1;
} else if (isJSXExpressionContainer(path.node)) {
startLine = path.node.expression.loc?.start.line ?? -1;
}
let prevPath = path.getPrevSibling();
if (!prevPath.node)
return;
while (!isJSXExpressionContainer(prevPath.node) || !isJSXEmptyExpression(prevPath.node.expression) || startLine !== -1) {
if (startLine !== -1 && isJSXExpressionContainer(prevPath.node)) {
if (isJSXEmptyExpression(prevPath.node.expression))
break;
const exprStartLine = prevPath.node.loc?.start.line ?? 0;
if (exprStartLine !== startLine) {
startLine = 0;
break;
}
}
prevPath = prevPath.getPrevSibling();
if (!prevPath.node || isJSXElement(prevPath.node)) {
if (isJSXElement(prevPath.node)) {
const jsxElementStartLine = prevPath.node.loc?.start.line ?? 0;
if (startLine === jsxElementStartLine) {
continue;
}
}
return;
}
}
if (!isJSXExpressionContainer(prevPath.node))
return;
const { expression } = prevPath.node;
if (isJSXEmptyExpression(expression)) {
comments = expression.innerComments ?? [];
}
} else {
comments = getComments(path);
}
const ignoreComment = Array.isArray(igc) ? igc : [igc];
if (!comments)
return;
const isIgnore = comments.some((comment) => ignoreComment.some((ig) => comment.value.includes(ig))) ?? false;
if (isIgnore) {
rawPath.skip();
}
}
// src/core/traverse/binary-expression.ts
function resolveBinaryExpression(path, options) {
const extra = path.node.extra ?? {};
const runtimeOptions = {};
if (options.autoDecimalOptions.toDecimal && !extra.__shouldTransform)
return;
if (extra.__shouldTransform) {
path.node.extra = extra.__extra;
processBinary(Object.assign(runtimeOptions, extra.options), path);
Object.assign(options, { needImport: runtimeOptions.needImport });
return;
}
processBinary(Object.assign(runtimeOptions, options, { initial: true }), path);
Object.assign(options, { needImport: runtimeOptions.needImport });
}
function processBinary(options, path) {
const { node } = path;
const { left, operator, right } = node;
if (!OPERATOR_KEYS.includes(operator))
return;
if (options.integer) {
return;
}
if (!options.autoDecimalOptions.toDecimal) {
if (shouldIgnoreComments(path)) {
path.skip();
return;
}
if (isStringSplicing(node, options) || mustTailPatchZero(node, options)) {
options.shouldSkip = true;
path.skip();
return;
}
}
if (isIntegerValue(left, path, options) && isIntegerValue(right, path, options)) {
options.integer = true;
return;
}
if (isNumericLiteral2(left) && isNumericLiteral2(right)) {
const decimalParts = [`new ${getPkgName(options)}(${left.value})`];
decimalParts.push(`.${OPERATOR[operator]}(${right.value})`);
if (options.initial && options.callMethod !== "decimal") {
decimalParts.push(`.${options.callMethod}${options.callArgs}`);
}
options.msa.overwriteNode(node, decimalParts.join(""));
resolveNeedImport(options);
path.skip();
return;
}
try {
options.ownerPath ??= path;
const leftNode = extractNodeValue(left, options);
const rightNode = extractNodeValue(right, options);
const leftIsInteger = leftNode.integer || isIntegerValue(left, path, options);
const rightIsInteger = rightNode.integer || isIntegerValue(right, path, options);
if (leftIsInteger && rightIsInteger) {
return;
}
if (leftNode.shouldSkip || rightNode.shouldSkip)
return;
const content = createDecimalOperation(leftNode.msa, rightNode.msa, operator, options);
options.msa.overwriteNode(node, content);
resolveNeedImport(options);
path.skip();
} catch (error) {
handleBinaryError(error);
}
}
function mustTailPatchZero(node, options) {
const { left, operator, right } = node;
if (operator !== "+")
return false;
if (isNumericLiteral2(left) && isNumericLiteral2(right))
return false;
if (!options.autoDecimalOptions.tailPatchZero)
return false;
if (options.initial && (!isNumericLiteral2(right) || right.value !== 0))
return true;
}
function isStringSplicing(node, options) {
const { left, operator, right } = node;
if (operator !== "+")
return false;
if (isNumericLiteral2(left) && isNumericLiteral2(right))
return false;
return [left, right].some((operand) => LITERALS.includes(operand.type) && isNonNumericLiteral(operand, options));
}
function isNonNumericLiteral(node, options) {
if (!LITERALS.includes(node.type))
return false;
if (node.type === "NullLiteral")
return true;
const { value } = node;
const { supportString } = options.autoDecimalOptions;
const isString = supportString ? Number.isNaN(Number(value)) : ["StringLiteral", "TemplateLiteral"].includes(node.type);
return node.type === "BooleanLiteral" || isString || value.trim() === "";
}
function shouldIgnoreComments(path) {
const comments = getComments(path);
return comments?.some((comment) => comment.value.includes(BASE_COMMENT));
}
function createDecimalOperation(leftAst, rightAst, operator, options) {
let leftContent = `new ${getPkgName(options)}(${leftAst.toString()})`;
if (leftAst.hasChanged()) {
leftContent = `${leftAst.toString()}`;
}
const generateContent = `${leftContent}.${OPERATOR[operator]}(${rightAst.toString()})`;
if (options.initial && options.callMethod !== "decimal") {
return `${generateContent}.${options.callMethod}${options.callArgs}`;
}
return generateContent;
}
function extractNodeValue(node, options) {
const codeSnippet = options.msa.snipNode(node).toString();
return getTransformed(
codeSnippet,
(transOptions) => ({
BinaryExpression: (path) => processBinary(Object.assign(transOptions, {
decimalPkgName: options.decimalPkgName,
integer: options.integer,
fromNewFunction: options.fromNewFunction,
needImport: options.needImport,
ownerPath: options.ownerPath
}), path)
}),
options.autoDecimalOptions
);
}
function handleBinaryError(error) {
if (error instanceof Error) {
throw new SyntaxError(`AutoDecimal compile error\uFF1A ${error.message}`);
}
throw error;
}
function resolveNeedImport(options) {
const supportNewFunction = options.autoDecimalOptions.supportNewFunction;
if (!options.fromNewFunction || options.fromNewFunction && !supportNewFunction.injectWindow) {
options.needImport = true;
}
}
// src/core/traverse/call-expression.ts
import { isBinaryExpression as isBinaryExpression2, isIdentifier as isIdentifier2, isMemberExpression as isMemberExpression2, isObjectExpression } from "@babel/types";
function resolveCallExpression(path, options) {
const { autoDecimalOptions } = options;
const { toDecimal, supportNewFunction } = autoDecimalOptions;
if (!toDecimal && !supportNewFunction)
return;
const { node } = path;
const { callee, arguments: args } = node;
if (supportNewFunction && isIdentifier2(callee) && callee.name === "Function") {
resolveNewFunctionExpression(path, options);
return;
}
if (!isMemberExpression2(callee))
return;
const toDecimalOptions = { ...DEFAULT_TO_DECIMAL_CONFIG };
if (toDecimal) {
mergeToDecimalOptions(toDecimalOptions, toDecimal);
}
const { property, object } = callee;
if (!isIdentifier2(property) || property.name !== toDecimalOptions.name)
return;
if (!isBinaryExpression2(path.parentPath.node) && !isBinaryExpression2(object)) {
throw new SyntaxError(`
line: ${path.parentPath.node.loc?.start.line}, ${options.msa.sliceNode(path.parentPath.node).toString()} \u6216 ${options.msa.sliceNode(object).toString()} \u4E0D\u662F\u6709\u6548\u7684\u8BA1\u7B97\u8868\u8FBE\u5F0F
`);
}
if (args && args.length > 0) {
const [arg] = args;
if (!isObjectExpression(arg)) {
throw new TypeError("toDecimal \u53C2\u6570\u9519\u8BEF");
}
const rawArg = options.msa.snipNode(arg).toString();
const jsonArg = rawArg.replace(/(\w+):/g, '"$1":').replace(/'/g, '"');
try {
const argToDecimalOptions = JSON.parse(jsonArg);
mergeToDecimalOptions(toDecimalOptions, argToDecimalOptions);
} catch (e) {
console.error(e);
}
}
let callArgs = "()";
if (toDecimalOptions.callMethod === "toFixed") {
callArgs = `(${toDecimalOptions.precision}, ${getRoundingMode(toDecimalOptions.roundingModes, autoDecimalOptions.package)})`;
}
const start = object.end ?? 0;
options.msa.remove(start, node.end ?? 0);
const resolveBinaryOptions = {
...options,
initial: true,
callArgs,
callMethod: toDecimalOptions.callMethod
};
if (isBinaryExpression2(object)) {
if (object.start !== node.start) {
options.msa.remove(node.start ?? 0, object.start ?? 0);
}
object.extra = {
...object.extra,
__extra: object.extra,
options: resolveBinaryOptions,
__shouldTransform: true
};
return;
}
const rootPath2 = getRootBinaryExprPath(path);
const runtimeOptions = {};
processBinary(Object.assign(runtimeOptions, resolveBinaryOptions), rootPath2);
Object.assign(options, { needImport: runtimeOptions.needImport });
}
// src/core/traverse/export-declaration.ts
import {
isBlockStatement,
isCallExpression as isCallExpression2,
isFunctionExpression as isFunctionExpression2,
isIdentifier as isIdentifier3,
isObjectExpression as isObjectExpression2,
isObjectMethod,
isObjectProperty,
isReturnStatement,
isSpreadElement
} from "@babel/types";
function resolveExportDefaultDeclaration(path, options) {
let { declaration } = path.node;
if (!isObjectExpression2(declaration) && !isCallExpression2(declaration))
return;
if (isCallExpression2(declaration)) {
const { arguments: args } = declaration;
const [objectExpr] = args;
if (!objectExpr || !isObjectExpression2(objectExpr))
return;
declaration = objectExpr;
}
const hasDataProperty = existDataProperty(declaration, options);
if (!hasDataProperty) {
const insertPosition = (declaration.start ?? 0) + 1;
const content = `
data() {
this.${options.decimalPkgName} = ${options.decimalPkgName};
},
`;
options.msa.prependLeft(insertPosition, content);
}
}
function existDataProperty(declaration, options) {
const { properties } = declaration;
return properties.some((prop) => {
if (isSpreadElement(prop))
return false;
if (isObjectProperty(prop) && !isFunctionExpression2(prop.value))
return false;
if (!isIdentifier3(prop.key) || isIdentifier3(prop.key) && prop.key.name !== "data")
return false;
const body = isObjectMethod(prop) ? prop.body : prop.value.body;
if (!isBlockStatement(body))
return false;
const returnStatement = body.body.find((item) => isReturnStatement(item));
if (!returnStatement)
return false;
const content = `
this.${options.decimalPkgName} = ${options.decimalPkgName};
`;
options.msa.prependLeft(returnStatement.start, content);
return true;
});
}
// src/core/traverse/import-declaration.ts
import { isIdentifier as isIdentifier4, isImportDefaultSpecifier as isImportDefaultSpecifier2, isImportNamespaceSpecifier as isImportNamespaceSpecifier2 } from "@babel/types";
function resolveImportDeclaration(path, options) {
if (path.node.source.value === PKG_NAME) {
const defaultDecimalPkgName = options.autoDecimalOptions.decimalName || DECIMAL_PKG_NAME;
options.imported = path.node.specifiers.some((spec) => {
if (isImportDefaultSpecifier2(spec)) {
if (spec.local.name !== defaultDecimalPkgName) {
options.decimalPkgName = spec.local.name;
}
return true;
}
if (isImportNamespaceSpecifier2(spec)) {
const pkgName = options.autoDecimalOptions.package === "big.js" ? "Big" : "Decimal";
options.decimalPkgName = `${spec.local.name}.${pkgName}`;
return true;
}
if (isIdentifier4(spec.imported) && spec.imported.name !== defaultDecimalPkgName) {
options.decimalPkgName = spec.local.name;
return true;
}
return false;
});
}
}
// src/core/traverse/new-function.ts
import { isArrayExpression, isAssignmentExpression, isCallExpression as isCallExpression3, isIdentifier as isIdentifier5, isMemberExpression as isMemberExpression3, isNodesEquivalent, isNumericLiteral as isNumericLiteral3, isObjectProperty as isObjectProperty2, isReturnStatement as isReturnStatement2, isStatement, isStringLiteral as isStringLiteral2, isVariableDeclarator as isVariableDeclarator2 } from "@babel/types";
function resolveNewFunctionExpression(path, options) {
if (!options.autoDecimalOptions.supportNewFunction)
return;
const { node } = path;
const { callee, arguments: args } = node;
if (!isIdentifier5(callee) || callee.name !== "Function")
return;
if (args.length === 0)
return;
const lastArg = args[args.length - 1];
resolveReturnParam(path, lastArg, options);
const { injectWindow } = options.autoDecimalOptions.supportNewFunction;
if (!injectWindow) {
provideDecimal(path, lastArg, options);
}
}
function resolveReturnParam(path, node, options) {
if (isStringNode(node)) {
return resolveStringTemplateLiteral(node, options);
}
if (isIdentifier5(node) || isCallExpression3(node) && isIdentifier5(node.callee)) {
const name = isIdentifier5(node) ? node.name : node.callee.name;
const binding = getScopeBinding(path, name);
resolveVariableParam(options, binding, name);
}
}
function resolveVariableParam(options, binding, name) {
if (!binding)
return;
if (binding.kind === "param") {
resolveVariableOfParam(binding, options, name);
}
const { constantViolations, path } = binding;
if (isVariableDeclarator2(path.node)) {
const { init } = path.node;
if (!init)
return;
resolveAssignmentExpression(path, init, options);
}
constantViolations.forEach((cv) => {
if (isAssignmentExpression(cv.node)) {
const { right } = cv.node;
resolveAssignmentExpression(cv, right, options);
}
});
}
function resolveAssignmentExpression(path, node, options) {
if (isStringNode(node)) {
return resolveStringTemplateLiteral(node, options);
}
if (isFunctionNode(node)) {
return resolveFunction(node, options);
}
if (isIdentifier5(node)) {
const binding = getScopeBinding(path, node.name);
return resolveVariableParam(options, binding);
}
if (isCallExpression3(node)) {
const variableName = node.callee.name;
const binding = getScopeBinding(path, variableName);
if (!binding)
return;
const pathNode = binding.path.node;
if (isFunctionNode(pathNode)) {
resolveFunction(pathNode, options);
return;
}
if (isVariableDeclarator2(pathNode) && isFunctionNode(pathNode.init)) {
resolveFunction(pathNode.init, options);
return;
}
console.warn(`\u672A\u5904\u7406\u7684\u8282\u70B9\uFF0Cline: ${node.loc.start.line}, ${node.loc.end.index}; column: ${node.loc.start.column}, ${node.loc.end.column}`);
}
}
function resolveVariableOfParam(binding, options, name) {
if (!isFunctionNode(binding.scope.block) || !name) {
return;
}
const { block, path } = binding.scope;
const { params } = block;
if (!params.length)
return;
const paramsIndex = params.findIndex((param) => param.name === name);
if (paramsIndex < 0)
return;
const fnName = getFunctionName(path);
if (!fnName || !path.parentPath)
return;
const parentBinding = getScopeBinding(path.parentPath, fnName);
if (!parentBinding)
return;
parentBinding.referencePaths.forEach((nodePath) => {
if (!isCallExpression3(nodePath.parent))
return;
const targetParams = nodePath.parent.arguments[paramsIndex];
if (!targetParams)
return;
resolveReturnParam(nodePath, targetParams, options);
});
}
function provideDecimal(path, node, options) {
if (!options.msa.hasChanged())
return;
let parentPath = path.parentPath;
let params;
const decimalParamsContent = `'${options.decimalPkgName}', ${options.msa.snipNode(node)}`;
const { parent } = path;
let callName = "";
if (isCallExpression3(parent)) {
options.msa.update(node.start, node.end, decimalParamsContent);
options.msa.update(parent.end - 1, parent.end, `, ${options.decimalPkgName})`);
return;
}
if (isAssignmentExpression(parent)) {
const { left } = parent;
if (isIdentifier5(left)) {
callName = left.name;
} else if (isMemberExpression3(left)) {
const binding2 = getScopeBinding(parentPath, left);
if (!binding2)
return;
binding2.referencePaths.forEach((reference) => {
const referenceParent = reference.parentPath.parent;
if (isCallExpression3(referenceParent)) {
options.msa.update(referenceParent.end - 1, referenceParent.end, `, ${options.decimalPkgName})`);
return;
}
if (isAssignmentExpression(referenceParent)) {
const { right } = referenceParent;
if (isNodesEquivalent(right, path.node)) {
options.msa.update(node.start, node.end, decimalParamsContent);
}
return;
}
if (isMemberExpression3(referenceParent)) {
const targetPath = getTargetPath(reference, isCallExpression3);
if (targetPath) {
options.msa.update(targetPath.node.end - 1, targetPath.node.end, `, ${options.decimalPkgName})`);
} else {
const targetPath2 = getTargetPath(reference, isAssignmentExpression);
if (!targetPath2)
return;
const { right } = targetPath2.node;
if (isNodesEquivalent(right, path.node)) {
options.msa.update(node.start, node.end, decimalParamsContent);
}
}
}
});
return;
}
} else if (!isVariableDeclarator2(parent)) {
parentPath = getTargetPath(path, isVariableDeclarator2);
if (!parentPath)
return;
if (isArrayExpression(parent)) {
params = path.key;
} else if (isObjectProperty2(parent)) {
params = parent.key.name;
}
}
if (!callName) {
if (!parentPath || !isVariableDeclarator2(parentPath.node)) {
return;
}
callName = parentPath.node.id?.name;
if (!callName)
return;
}
const binding = getScopeBinding(path, callName);
if (!binding?.referenced)
return;
options.msa.update(node.start, node.end, decimalParamsContent);
binding.referencePaths.forEach((referencePath) => {
const { parent: parent2 } = referencePath;
if (isCallExpression3(parent2)) {
options.msa.update(parent2.end - 1, parent2.end, `, ${options.decimalPkgName})`);
return;
}
if (isMemberExpression3(parent2)) {
if (isNumericLiteral3(parent2.property) || isIdentifier5(parent2.property)) {
const targetParams = isNumericLiteral3(parent2.property) ? parent2.property.value : parent2.property.name;
if (targetParams !== params) {
return;
}
const targetPath = getTargetPath(referencePath, isCallExpression3);
if (!targetPath)
return;
options.msa.update(targetPath.node.end - 1, targetPath.node.end, `, ${options.decimalPkgName})`);
}
}
});
}
function resolveStringTemplateLiteral(node, options) {
let rawString = "";
let quote = "'";
if (isStringLiteral2(node)) {
rawString = node.value;
} else {
quote = "`";
rawString = options.msa.snipNode(node).toString().slice(1, -1);
}
const { autoDecimalOptions } = options;
const supportNewFunction = autoDecimalOptions.supportNewFunction;
const code = RETURN_DECLARATION_FN.replace(RETURN_DECLARATION_CODE, rawString);
const toDecimalParams = supportNewFunction.toDecimal ?? false;
const runtimeOptions = {};
const { msa: transformedMsa } = getTransformed(code, (opts) => traverseAst(Object.assign(runtimeOptions, opts, {
fromNewFunction: true,
needImport: options.needImport
}), false), {
...autoDecimalOptions,
toDecimal: toDecimalParams
});
if (transformedMsa.hasChanged()) {
Object.assign(options, {
fromNewFunction: runtimeOptions.fromNewFunction,
needImport: runtimeOptions.needImport
});
const result = transformedMsa.toString().replace(RETURN_DECLARATION_PREFIX, "").slice(0, -1);
options.msa.overwriteNode(node, `${quote}${result}${quote}`);
}
}
function resolveFunction(node, options) {
const { body } = node;
if (isStringNode(body)) {
resolveStringTemplateLiteral(body, options);
return;
}
if (isStatement(body)) {
const lastNode = body.body[body.body.length - 1];
if (!isReturnStatement2(lastNode)) {
return;
}
const { argument } = lastNode;
if (!argument || !isStringNode(argument)) {
return;
}
resolveStringTemplateLiteral(argument, options);
}
}
// src/core/traverse/ast.ts
function traverseAst(options, checkImport = true, templateImport = false) {
return {
enter(path) {
switch (path.type) {
case "Program":
case "ImportDeclaration":
case "ExportDefaultDeclaration":
case "JSXElement":
case "JSXOpeningElement":
case "JSXExpressionContainer":
case "BinaryExpression":
break;
default:
blockComment(path);
nextComment(path);
break;
}
},
Program: {
enter(path) {
const file = path.parent;
const fileIgnore = file.comments?.some((comment) => comment.value.includes(FILE_COMMENT)) ?? false;
options.imported = fileIgnore && templateImport;
if (fileIgnore && !templateImport) {
path.skip();
}
},
exit() {
const hasChanged = options.msa.hasChanged();
if (!checkImport || options.imported || !hasChanged && !templateImport) {
return;
}
if (!options.needImport)
return;
const pkgName = options.autoDecimalOptions?.package ?? PKG_NAME;
options.imported = true;
options.msa.prepend(`
import ${options.decimalPkgName} from '${pkgName}';
`);
}
},
ExportDefaultDeclaration(path) {
if (!templateImport)
return;
resolveExportDefaultDeclaration(path, options);
},
ImportDeclaration(path) {
if (options.imported)
return;
resolveImportDeclaration(path, options);
},
JSXElement: (path) => innerComment(path, BLOCK_COMMENT),
JSXOpeningElement: (path) => {
if (!path.node.attributes.length)
return;
innerComment(path);
},
JSXExpressionContainer: (path) => {
if (isJSXEmptyExpression2(path.node.expression))
return;
innerComment(path);
},
BinaryExpression: (path) => resolveBinaryExpression(path, options),
CallExpression: (path) => resolveCallExpression(path, options),
NewExpression: (path) => resolveNewFunctionExpression(path, options)
};
}
// src/core/transform.ts
function transformAutoDecimal(code, autoDecimalOptions) {
const { msa } = getTransformed(code, traverseAst, autoDecimalOptions);
return msa;
}
function transformVueAutoDecimal(code, autoDecimalOptions) {
const sfcAst = vueParse(code);
const { descriptor } = sfcAst;
const { script, scriptSetup, template } = descriptor;
const msa = new MagicStringAST(code);
const getDecimalPkgName = (scriptSection) => {
if (!scriptSection)
return autoDecimalOptions.decimalName || DECIMAL_PKG_NAME;
const { decimalPkgName: decimalPkgName2 } = getTransformed(
scriptSection.content,
(options) => ({
ImportDeclaration: (path) => resolveImportDeclaration(path, options)
}),
autoDecimalOptions
);
return decimalPkgName2;
};
let decimalPkgName = getDecimalPkgName(scriptSetup);
if (!decimalPkgName) {
decimalPkgName = getDecimalPkgName(script);
}
function parserTemplate(children) {
const commentState = { line: 0, block: false, next: false };
children.forEach((child) => {
if (child.type === NodeTypes.TEXT)
return;
if (child.type === NodeTypes.COMMENT) {
updateCommentState(child, commentState);
return;
}
if (shouldSkipComment(child, commentState, "block"))
return;
switch (child.type) {
case NodeTypes.INTERPOLATION:
handleInterpolation(child, commentState);
break;
case NodeTypes.ELEMENT:
handleElementProps(child, commentState);
break;
default:
break;
}
if (hasChildrenNode(child) && child.children) {
parserTemplate(child.children);
}
});
}
function hasChildrenNode(child) {
const nodeTypes = [NodeTypes.ELEMENT, NodeTypes.COMPOUND_EXPRESSION, NodeTypes.IF_BRANCH, NodeTypes.FOR];
return nodeTypes.includes(child.type);
}
function updateCommentState(commentNode, commentState) {
commentState.line = commentNode.loc.start.line;
commentState.block = commentNode.content.includes(BLOCK_COMMENT);
commentState.next = commentNode.content.includes(NEXT_COMMENT);
}
function handleInterpolation(interpolationNode, commentState) {
if (shouldSkipComment(interpolationNode, commentState))
return;
if (interpolationNode.content.type === NodeTypes.COMPOUND_EXPRESSION)
return;
const expContent = interpolationNode.content.content;
if (!expContent || !existTargetOperator(expContent))
return;
const { msa: transformedMsa } = getTransformed(
expContent,
(options) => traverseAst({ ...options, decimalPkgName }, false),
autoDecimalOptions
);
msa.update(interpolationNode.content.loc.start.offset, interpolationNode.content.loc.end.offset, transformedMsa.toString());
}
function handleElementProps(elementNode, commentState) {
if (shouldSkipComment(elementNode, commentState))
return;
if (!elementNode.props.length)
return;
elementNode.props.forEach((prop) => {
if (prop.type === NodeTypes.ATTRIBUTE)
return;
if (!prop.exp || prop.exp.type === NodeTypes.COMPOUND_EXPRESSION)
return;
const { loc } = prop.exp;
let isObjExpr = false;
let content = prop.exp.content;
if (!content || !existTargetOperator(content))
return;
if (isBuiltInDirective(prop))
return;
if (prop.exp.ast && isObjectExpression3(prop.exp.ast)) {
isObjExpr = true;
content = `${PATCH_DECLARATION}${content}`;
}
const { msa: transformedMsa } = getTransformed(
content,
(options) => traverseAst({ ...options, decimalPkgName }, false),
autoDecimalOptions
);
if (isObjExpr) {
transformedMsa.remove(0, PATCH_DECLARATION.length);
}
msa.update(loc.start.offset, loc.end.offset, transformedMsa.toString());
});
}
function isBuiltInDirective(prop) {
return ["for", "html", "text"].includes(prop.name);
}
function existTargetOperator(content) {
return OPERATOR_KEYS.some((key) => content.includes(key));
}
function shouldSkipComment(child, comment, property = "next") {
return comment[property] && comment.line + 1 === child.loc.start.line;
}
if (template) {
const { ast, attrs = {} } = template;
if (!attrs["ad-ignore"] && ast?.children) {
parserTemplate(ast.children);
}
}
let needsImport = msa.hasChanged();
const parseScript = (scriptSection) => {
if (!scriptSection)
return;
const { start, end } = scriptSection.loc;
const { msa: transformedMsa, imported } = getTransformed(
scriptSection.content,
(options) => traverseAst(options, true, needsImport),
autoDecimalOptions
);
if (needsImport) {
needsImport = !imported;
}
msa.update(start.offset, end.offset, transformedMsa.toString());
};
parseScript(scriptSetup);
parseScript(script);
if (needsImport) {
msa.append(`
<script>
import ${DECIMAL_PKG_NAME} from '${PKG_NAME}';
export default {
data() {
this.${DECIMAL_PKG_NAME} = ${DECIMAL_PKG_NAME};
}
}
</script>
`);
}
return msa;
}
function getTransformed(code, traverseOptions, autoDecimalOptions) {
const ast = parse(code, {
sourceType: "module",
plugins: ["typescript", "jsx"]
});
const msa = new MagicStringAST(code);
const options = {
autoDecimalOptions,
imported: false,
msa,
decimalPkgName: autoDecimalOptions.decimalName || DECIMAL_PKG_NAME,
initial: false,
integer: false,
shouldSkip: false,
callArgs: "()",
callMethod: "toNumber",
needImport: false,
fromNewFunction: false
};
const babelTraverse = traverse.default ?? traverse;
babelTraverse(ast, traverseOptions(options));
return options;
}
// src/core/unplugin.ts
function transform(code, id, options) {
let msa;
if (REGEX_VUE.some((reg) => reg.test(id))) {
msa = transformVueAutoDecimal(code, options);
} else {
msa = transformAutoDecimal(code, options);
}
if (!msa.hasChanged())
return;
return {
code: msa.toString(),
map: msa.generateMap({ source: id, includeContent: true, hires: true })
};
}
var unplugin_default = createUnplugin((rawOptions) => {
const filter = createFilter(
[REGEX_SUPPORTED_EXT, ...REGEX_VUE],
[REGEX_NODE_MODULES]
);
const options = resolveOptions(rawOptions);
if (options.dts) {
generateDeclaration(options);
}
return {
name: "unplugin-auto-decimal",
enforce: "pre",
transformInclude(id) {
return filter(id);
},
transform(code, id) {
const pkgName = options.package ?? PKG_NAME;
if (!isPackageExists2(pkgName)) {
console.error(`[AutoDecimal] \u8BF7\u5148\u5B89\u88C5 ${pkgName}`);
return { code };
}
return transform(code, id, options);
}
};
});
export {
unplugin_default
};