@tevm/ts-plugin
Version:
A typescript plugin for tevm
596 lines (581 loc) • 20.3 kB
JavaScript
import { createRequire } from 'module';
import path from 'path';
import { mkdirSync, statSync, writeFileSync, readFileSync, existsSync } from 'fs';
import { bundler } from '@tevm/base-bundler';
import * as solc from 'solc';
import { findAll } from 'solidity-ast/utils.js';
import { minimatch } from 'minimatch';
import { writeFile, mkdir, stat, readFile, access } from 'fs/promises';
import { createCache } from '@tevm/bundler-cache';
import { loadConfig, defaultConfig } from '@tevm/config';
import { runSync, catchTag, logWarning, map } from 'effect/Effect';
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// src/utils/isSolidity.ts
var isSolidity;
var init_isSolidity = __esm({
"src/utils/isSolidity.ts"() {
isSolidity = (fileName) => fileName.endsWith(".sol") && !fileName.endsWith("/.sol") && fileName !== ".sol";
}
});
// src/utils/isRelativeSolidity.ts
var isRelative, isRelativeSolidity;
var init_isRelativeSolidity = __esm({
"src/utils/isRelativeSolidity.ts"() {
init_isSolidity();
isRelative = (fileName) => fileName.startsWith("./") || fileName.startsWith("../");
isRelativeSolidity = (fileName) => isRelative(fileName) && isSolidity(fileName);
}
});
var solidityModuleResolver;
var init_solidityModuleResolver = __esm({
"src/utils/solidityModuleResolver.ts"() {
init_isRelativeSolidity();
init_isSolidity();
solidityModuleResolver = (moduleName, ts, createInfo, containingFile) => {
if (isRelativeSolidity(moduleName)) {
return {
extension: ts.Extension.Dts,
isExternalLibraryImport: false,
resolvedFileName: path.resolve(path.dirname(containingFile), moduleName)
};
}
if (isSolidity(moduleName)) {
return {
extension: ts.Extension.Dts,
isExternalLibraryImport: false,
resolvedFileName: createRequire(path.dirname(containingFile)).resolve(moduleName)
};
}
if (moduleName.startsWith("@tevm/contract")) {
const result = ts.resolveModuleName(
moduleName,
containingFile,
createInfo.project.getCompilerOptions(),
createInfo.project
);
if (result.resolvedModule) {
return {
extension: ts.Extension.Dts,
isExternalLibraryImport: true,
resolvedFileName: result.resolvedModule.resolvedFileName
};
}
console.error("Could not resolve module. Is tevm/core installed?", moduleName, result);
return void 0;
}
return void 0;
};
}
});
// src/utils/findNode.ts
function findNode(rootNode, position) {
if (position < 0) {
throw new Error("Position must be non-negative");
}
if (Number.isInteger(position) === false) {
throw new Error("Position must be an integer");
}
let foundNode = null;
function visit(node) {
if (position >= node.getStart() && position <= node.getEnd()) {
foundNode = node;
node.forEachChild(visit);
}
}
rootNode.forEachChild(visit);
return foundNode;
}
var init_findNode = __esm({
"src/utils/findNode.ts"() {
}
});
// src/utils/invariant.ts
function invariant(condition, message) {
if (!condition) {
throw new Error(message);
}
}
var init_invariant = __esm({
"src/utils/invariant.ts"() {
}
});
function convertSolcAstToTsDefinitionInfo(astNode, fileName, containerName, solcInput, ts) {
var _a;
const [start, length] = astNode.src.split(":").map(Number);
let kind = ts.ScriptElementKind.unknown;
let name = "unknown";
if (astNode.nodeType === "VariableDeclaration") {
kind = ts.ScriptElementKind.variableElement;
name = astNode.name;
} else if (astNode.nodeType === "FunctionDefinition") {
kind = ts.ScriptElementKind.functionElement;
name = astNode.name;
}
const inputLength = (_a = solcInput.sources[fileName].content) == null ? void 0 : _a.length;
const actualLength = readFileSync(fileName, "utf8").length;
const offset = inputLength - actualLength;
return {
fileName,
textSpan: ts.createTextSpan(start - offset, length),
kind,
name,
containerKind: ts.ScriptElementKind.classElement,
containerName
};
}
var init_convertSolcAstToTsDefinitionInfo = __esm({
"src/utils/convertSolcAstToTsDefinitionInfo.ts"() {
}
});
// src/utils/findContractDefinitionFileNameFromTevmNode.ts
function findContractDefinitionFileNameFromTevmNode(node, languageService, fileName, ts) {
let current = node;
while (current) {
if (!ts.isPropertyAccessExpression(current)) {
current = current.parent;
continue;
}
const parent = current.expression;
if (!ts.isCallExpression(parent)) {
current = current.parent;
continue;
}
const grandParent = parent.expression;
if (!ts.isPropertyAccessExpression(grandParent)) {
current = current.parent;
continue;
}
if (!["read", "write", "events"].includes(grandParent.name.getText())) {
current = current.parent;
continue;
}
const contractNode = grandParent.expression;
const contractDefinition = languageService.getDefinitionAtPosition(fileName, contractNode.getStart());
if (!contractDefinition || contractDefinition.length === 0) {
current = current.parent;
continue;
}
const out = contractDefinition[0].fileName;
if (!out.endsWith(".sol")) {
current = current.parent;
continue;
}
return out;
}
return null;
}
var init_findContractDefinitionFileNameFromTevmNode = __esm({
"src/utils/findContractDefinitionFileNameFromTevmNode.ts"() {
}
});
// src/utils/index.ts
var init_utils = __esm({
"src/utils/index.ts"() {
init_isRelativeSolidity();
init_isSolidity();
init_solidityModuleResolver();
init_findNode();
init_invariant();
init_convertSolcAstToTsDefinitionInfo();
init_findContractDefinitionFileNameFromTevmNode();
}
});
var getDefinitionServiceDecorator;
var init_getDefinitionAtPosition = __esm({
"src/decorators/getDefinitionAtPosition.ts"() {
init_utils();
init_utils();
getDefinitionServiceDecorator = (service, config, logger, ts, fao, solcCache) => {
const getDefinitionAtPosition = (fileName, position) => {
var _a, _b;
const definition = service.getDefinitionAtPosition(fileName, position);
const sourceFile = (_a = service.getProgram()) == null ? void 0 : _a.getSourceFile(fileName);
const node = sourceFile && findNode(sourceFile, position);
const ContractPath = node && findContractDefinitionFileNameFromTevmNode(node, service, fileName, ts);
if (!ContractPath) {
return definition;
}
const plugin = bundler(config, logger, fao, solc, solcCache);
const includedAst = true;
const { asts, solcInput } = plugin.resolveDtsSync(ContractPath, process.cwd(), includedAst, false);
if (!asts) {
logger.error(`@tevm/ts-plugin: getDefinitionAtPositionDecorator was unable to resolve asts for ${ContractPath}`);
return definition;
}
const definitions = [];
for (const [fileName2, ast] of Object.entries(asts)) {
for (const functionDef of findAll("EventDefinition", ast)) {
if (functionDef.name === (node == null ? void 0 : node.getText())) {
definitions.push({
node: functionDef,
fileName: fileName2
});
}
}
for (const functionDef of findAll("FunctionDefinition", ast)) {
if (functionDef.name === (node == null ? void 0 : node.getText())) {
definitions.push({
node: functionDef,
fileName: fileName2
});
}
}
}
if (!definitions.length) {
logger.error(`@tevm/ts-plugin: unable to find definitions ${ContractPath}`);
return definition;
}
const contractName = ((_b = ContractPath.split("/").pop()) == null ? void 0 : _b.split(".")[0]) ?? "Contract";
if (!solcInput) {
logger.error(`@tevm/ts-plugin: solcInput is undefined for ${ContractPath}`);
return definition;
}
return [
...definitions.map(
({ fileName: fileName2, node: node2 }) => convertSolcAstToTsDefinitionInfo(node2, fileName2, contractName, { sources: solcInput.sources }, ts)
),
...definition ?? []
];
};
const getDefinitionAndBoundSpan = (fileName, position) => {
var _a;
const definitions = getDefinitionAtPosition(fileName, position);
if (!definitions) {
return service.getDefinitionAndBoundSpan(fileName, position);
}
if (!definitions.some((definition) => definition.fileName.endsWith(".sol"))) {
return service.getDefinitionAndBoundSpan(fileName, position);
}
const sourceFile = (_a = service.getProgram()) == null ? void 0 : _a.getSourceFile(fileName);
const node = sourceFile && findNode(sourceFile, position);
const textSpan = node ? ts.createTextSpanFromBounds(node.getStart(), node.getEnd()) : void 0;
return {
definitions,
textSpan: textSpan ?? ts.createTextSpan(0, 0)
// Fallback to a zero-length span
};
};
return new Proxy(service, {
get(target, key) {
if (key === "getDefinitionAtPosition") {
return getDefinitionAtPosition;
}
if (key === "getDefinitionAndBoundSpan") {
return getDefinitionAndBoundSpan;
}
return target[key];
}
});
};
}
});
// src/factories/logger.ts
var createLogger;
var init_logger = __esm({
"src/factories/logger.ts"() {
createLogger = (pluginCreateInfo) => {
const info = (msg) => pluginCreateInfo.project.projectService.logger.info(`[tevm-ts-plugin] ${msg}`);
const warn = (msg) => pluginCreateInfo.project.projectService.logger.info(`[tevm-ts-plugin] warning: ${msg}`);
const error = (msg) => pluginCreateInfo.project.projectService.logger.info(`[tevm-ts-plugin] error: ${msg}`);
const log = (msg) => pluginCreateInfo.project.projectService.logger.info(`[tevm-ts-plugin] log: ${msg}`);
return { info, warn, error, log };
};
}
});
// src/factories/decorator.ts
var createHostDecorator, decorateHost;
var init_decorator = __esm({
"src/factories/decorator.ts"() {
createHostDecorator = (decorator) => {
return (createInfo, ...rest) => {
const proxy = decorator(createInfo, ...rest);
return new Proxy(createInfo.languageServiceHost, {
get(target, key) {
if (key in proxy) {
return proxy[key];
}
return target[key];
}
});
};
};
decorateHost = (...decorators) => {
return (createInfo, ...rest) => {
if (decorators.length === 0) {
return createInfo.languageServiceHost;
}
const [nextDecorator, ...restDecorators] = decorators;
const decoratedHost = nextDecorator(createInfo, ...rest);
const decoratedCreateInfo = new Proxy(createInfo, {
get(target, key) {
if (key === "languageServiceHost") {
return decoratedHost;
}
return target[key];
}
});
return decorateHost(...restDecorators)(decoratedCreateInfo, ...rest);
};
};
}
});
// src/factories/index.ts
var init_factories = __esm({
"src/factories/index.ts"() {
init_logger();
init_decorator();
}
});
// src/decorators/getScriptKind.ts
var getScriptKindDecorator;
var init_getScriptKind = __esm({
"src/decorators/getScriptKind.ts"() {
init_factories();
init_utils();
getScriptKindDecorator = createHostDecorator((createInfo, ts, logger, config) => {
return {
getScriptKind: (fileName) => {
if (isRelativeSolidity(fileName)) {
return ts.ScriptKind.TS;
}
if (isSolidity(fileName)) {
return ts.ScriptKind.External;
}
if (!createInfo.languageServiceHost.getScriptKind) {
return ts.ScriptKind.Unknown;
}
return createInfo.languageServiceHost.getScriptKind(fileName);
}
};
});
}
});
var resolveJsonAsConst;
var init_resolveJsonAsConst = __esm({
"src/utils/resolveJsonAsConst.ts"() {
resolveJsonAsConst = (config, jsonFilePath, fao, languageServiceHost, ts) => {
for (const matcher of config.jsonAsConst) {
if (minimatch(jsonFilePath, matcher)) {
const jsonString = fao.readFileSync(jsonFilePath, "utf8");
return ts.ScriptSnapshot.fromString(`export default ${jsonString} as const`);
}
}
return languageServiceHost.getScriptSnapshot(jsonFilePath);
};
}
});
var getScriptSnapshotDecorator;
var init_getScriptSnapshot = __esm({
"src/decorators/getScriptSnapshot.ts"() {
init_factories();
init_utils();
init_resolveJsonAsConst();
getScriptSnapshotDecorator = (solcCache) => createHostDecorator(({ languageServiceHost }, ts, logger, config, fao) => {
return {
getScriptSnapshot: (filePath) => {
if (filePath.endsWith(".json")) {
return resolveJsonAsConst(config, filePath, fao, languageServiceHost, ts);
}
if (!isSolidity(filePath) || !existsSync(filePath) || existsSync(`${filePath}.d.ts`) || existsSync(`${filePath}.ts`)) {
return languageServiceHost.getScriptSnapshot(filePath);
}
try {
const plugin = bundler(config, logger, fao, solc, solcCache);
const resolveBytecode = filePath.endsWith(".s.sol");
const snapshot = plugin.resolveDtsSync(filePath, process.cwd(), false, resolveBytecode);
if (config.debug) {
writeFileSync(
`${filePath}.debug.d.ts`,
`// Debug: the following snapshot is what tevm resolves ${filePath} to
${snapshot.code}`
);
}
return ts.ScriptSnapshot.fromString(snapshot.code);
} catch (e) {
logger.error(`@tevm/ts-plugin: getScriptSnapshotDecorator was unable to resolve dts for ${filePath}`);
logger.error(e);
return ts.ScriptSnapshot.fromString("export {}");
}
}
};
});
}
});
// src/decorators/resolveModuleNameLiterals.ts
var resolveModuleNameLiteralsDecorator;
var init_resolveModuleNameLiterals = __esm({
"src/decorators/resolveModuleNameLiterals.ts"() {
init_factories();
init_utils();
init_invariant();
resolveModuleNameLiteralsDecorator = createHostDecorator((createInfo, ts, logger, config) => {
return {
resolveModuleNameLiterals: (moduleNames, containingFile, ...rest) => {
var _a, _b;
const resolvedModules = (_b = (_a = createInfo.languageServiceHost).resolveModuleNameLiterals) == null ? void 0 : _b.call(
_a,
moduleNames,
containingFile,
...rest
);
return moduleNames.map(({ text: moduleName }, index) => {
let remappedName = moduleName;
Object.entries(config.remappings).forEach(([from, to]) => {
if (moduleName.startsWith(from)) {
remappedName = moduleName.replace(from, to);
}
});
invariant(resolvedModules, 'Expected "resolvedModules" to be defined.');
try {
const resolvedModule = solidityModuleResolver(remappedName, ts, createInfo, containingFile);
if (resolvedModule) {
return { resolvedModule };
}
return resolvedModules[index];
} catch (e) {
logger.error(e);
return resolvedModules[index];
}
});
}
};
});
}
});
// src/decorators/index.ts
var init_decorators = __esm({
"src/decorators/index.ts"() {
init_getScriptKind();
init_getScriptSnapshot();
init_resolveModuleNameLiterals();
}
});
var createFileAccessObject, createRealFileAccessObject;
var init_fileAccessObject = __esm({
"src/factories/fileAccessObject.ts"() {
createFileAccessObject = (lsHost) => {
return {
existsSync: (fileName) => lsHost.fileExists(fileName),
readFileSync: (fileName, encoding) => {
const file = lsHost.readFile(fileName, encoding);
if (!file) {
throw new Error(`@tevm/ts-plugin: unable to read file ${fileName}`);
}
return file;
},
writeFileSync: (fileName, data) => {
var _a;
(_a = lsHost.writeFile) == null ? void 0 : _a.call(lsHost, fileName, data);
},
// TODO clean this up. This works fine only because only the cache needs them and the cache is operating on a real file system and not a virtual one
// These are just stubs to match interface since making multiple interfaces is tedious atm
exists: async (fileName) => {
return lsHost.fileExists(fileName);
},
readFile: async (fileName, encoding) => {
const file = lsHost.readFile(fileName, encoding);
if (!file) {
throw new Error(`@tevm/ts-plugin: unable to read file ${fileName}`);
}
return file;
},
stat,
statSync,
mkdirSync,
mkdir,
writeFile
};
};
createRealFileAccessObject = () => {
return {
readFile,
existsSync: existsSync,
readFileSync: readFileSync,
writeFileSync: writeFileSync,
statSync,
stat,
mkdirSync,
mkdir,
writeFile,
exists: async (fileName) => {
try {
await access(fileName);
return true;
} catch (e) {
return false;
}
}
};
};
}
});
var tsPlugin;
var init_tsPlugin = __esm({
"src/tsPlugin.ts"() {
init_getDefinitionAtPosition();
init_decorators();
init_fileAccessObject();
init_factories();
init_utils();
tsPlugin = (modules) => {
return {
create: (createInfo) => {
const logger = createLogger(createInfo);
const config = runSync(
loadConfig(createInfo.project.getCurrentDirectory()).pipe(
catchTag(
"FailedToReadConfigError",
() => logWarning("Unable to find tevm.config.json. Using default config.").pipe(map(() => defaultConfig))
)
)
);
const fao = createFileAccessObject(createInfo.languageServiceHost);
const cache = createCache(
config.cacheDir,
// this fao uses real file system
// TODO we want to handle the case where fs doesn't exist
createRealFileAccessObject(),
createInfo.project.getCurrentDirectory()
);
const service = getDefinitionServiceDecorator(
modules.typescript.createLanguageService(
decorateHost(getScriptKindDecorator, resolveModuleNameLiteralsDecorator, getScriptSnapshotDecorator(cache))(
createInfo,
modules.typescript,
logger,
config,
fao
)
),
config,
logger,
modules.typescript,
fao,
cache
);
return service;
},
getExternalFiles: (project) => {
return project.getFileNames().filter(isSolidity);
}
};
};
}
});
// src/index.ts
var require_index = __commonJS({
"src/index.ts"(exports, module) {
init_tsPlugin();
module.exports = tsPlugin;
}
});
var index = require_index();
export { index as default };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map