fix flatten with remappings

pull/4530/head
yann300 9 months ago
parent 04318fcda1
commit 8558a9b072
  1. 2
      apps/remix-ide/ci/makeMockCompiler.js
  2. 9
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  3. 2
      libs/remix-solidity/src/index.ts
  4. 148
      libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities.ts

@ -2,7 +2,7 @@
var fs = require('fs') var fs = require('fs')
var compiler = require('solc') var compiler = require('solc')
var compilerInput = require('@remix-project/remix-solidity').CompilerInput var compilerInput = require('@remix-project/remix-solidity').compilerInputFactory
var defaultVersion = 'soljson-v0.8.24+commit.e11b9ed9.js' var defaultVersion = 'soljson-v0.8.24+commit.e11b9ed9.js'
const path = require('path') const path = require('path')

@ -2,6 +2,7 @@ import React from 'react'
import {Plugin} from '@remixproject/engine' import {Plugin} from '@remixproject/engine'
import {customAction} from '@remixproject/plugin-api' import {customAction} from '@remixproject/plugin-api'
import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler' import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler'
import type {CompilerInput, CompilationSource } from '@remix-project/remix-solidity'
const _paq = (window._paq = window._paq || []) const _paq = (window._paq = window._paq || [])
@ -25,7 +26,7 @@ export class ContractFlattener extends Plugin {
if (data.sources && Object.keys(data.sources).length > 1) { if (data.sources && Object.keys(data.sources).length > 1) {
if (this.triggerFlattenContract) { if (this.triggerFlattenContract) {
this.triggerFlattenContract = false this.triggerFlattenContract = false
await this.flattenContract(source, file, data) await this.flattenContract(source, file, data, JSON.parse(input))
} }
} }
}) })
@ -47,17 +48,17 @@ export class ContractFlattener extends Plugin {
* Takes the flattened result, writes it to a file and returns the result. * Takes the flattened result, writes it to a file and returns the result.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
async flattenContract(source: {sources: any; target: string}, filePath: string, data: {contracts: any; sources: any}): Promise<string> { async flattenContract(source: {sources: any; target: string}, filePath: string, data: {contracts: any; sources: any}, input: CompilerInput): Promise<string> {
const appendage = '_flattened.sol' const appendage = '_flattened.sol'
const normalized = normalizeContractPath(filePath) const normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}` const path = `${normalized[normalized.length - 2]}${appendage}`
const ast = data.sources const ast: { [contractName: string]: CompilationSource } = data.sources
let dependencyGraph let dependencyGraph
let sorted let sorted
let result let result
let sources let sources
try { try {
dependencyGraph = getDependencyGraph(ast, filePath) dependencyGraph = getDependencyGraph(ast, filePath, input.settings.remappings)
sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse() sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
sources = source.sources sources = source.sources
result = concatSourceFiles(sorted, sources) result = concatSourceFiles(sorted, sources)

@ -1,6 +1,6 @@
export { Compiler } from './compiler/compiler' export { Compiler } from './compiler/compiler'
export { compile } from './compiler/compiler-helpers' export { compile } from './compiler/compiler-helpers'
export { default as CompilerInput, getValidLanguage } from './compiler/compiler-input' export { default as compilerInputFactory, getValidLanguage } from './compiler/compiler-input'
export { CompilerAbstract } from './compiler/compiler-abstract' export { CompilerAbstract } from './compiler/compiler-abstract'
export * from './compiler/types' export * from './compiler/types'
export { pathToURL, baseURLBin, baseURLWasm, canUseWorker, urlFromVersion } from './compiler/compiler-utils' export { pathToURL, baseURLBin, baseURLWasm, canUseWorker, urlFromVersion } from './compiler/compiler-utils'

@ -1,11 +1,14 @@
import type {CompilationSource, AstNode} from '@remix-project/remix-solidity'
const IMPORT_SOLIDITY_REGEX = /^\s*import(\s+).*$/gm; const IMPORT_SOLIDITY_REGEX = /^\s*import(\s+).*$/gm;
const SPDX_SOLIDITY_REGEX = /^\s*\/\/ SPDX-License-Identifier:.*$/gm; const SPDX_SOLIDITY_REGEX = /^\s*\/\/ SPDX-License-Identifier:.*$/gm;
export function getDependencyGraph(ast, target) { type Visited = { [key: string]: number }
export function getDependencyGraph(ast: { [name: string]: CompilationSource }, target: string, remappings: string[]) {
const graph = tsort(); const graph = tsort();
const visited = {}; const visited = {};
visited[target] = 1; visited[target] = 1;
_traverse(graph, visited, ast, target); _traverse(graph, visited, ast, target, remappings);
return graph; return graph;
} }
@ -21,32 +24,31 @@ export function concatSourceFiles(files: any[], sources: any) {
return concat; return concat;
} }
function _traverse(graph, visited, ast, name) { function _traverse(graph: Graph, visited: Visited, ast: { [name: string]: CompilationSource }, name: string, remappings: string[]) {
let currentAst = null let currentAst = null
currentAst = ast[name].ast currentAst = ast[name].ast
const dependencies = _getDependencies(currentAst); const dependencies = _getDependencies(currentAst);
for (const dependency of dependencies) { for (const dependency of dependencies) {
const path = resolve(name, dependency); const path = resolve(name, dependency, remappings);
if (path in visited) { if (path in visited) {
// continue; // fixes wrong ordering of source in flattened file // continue; // fixes wrong ordering of source in flattened file
} }
visited[path] = 1; visited[path] = 1;
graph.add(name, path); graph.add(name, path);
_traverse(graph, visited, ast, path); _traverse(graph, visited, ast, path, remappings);
} }
} }
function _getDependencies(ast) { function _getDependencies(ast: AstNode) {
const dependencies = ast?.nodes const dependencies = ast?.nodes
.filter(node => node?.nodeType === 'ImportDirective') .filter(node => node?.nodeType === 'ImportDirective')
.map(node => node?.file); .map(node => node?.file);
return dependencies; return dependencies;
} }
// TSORT // TSORT
function tsort(initial?: any) { function tsort(initial?: any): Graph {
const graph = new Graph(); const graph = new Graph();
if (initial) { if (initial) {
@ -58,78 +60,88 @@ function tsort(initial?: any) {
return graph; return graph;
} }
class Graph {
nodes: { [key: string]: any}
constructor() {
this.nodes = {}
}
function Graph() { // Add sorted items to the graph
this.nodes = {}; add (name, path) {
} const self = this;
// eslint-disable-next-line prefer-rest-params
// Add sorted items to the graph let items = [].slice.call(arguments);
Graph.prototype.add = function () {
const self = this; if (items.length === 1 && Array.isArray(items[0]))
// eslint-disable-next-line prefer-rest-params items = items[0];
let items = [].slice.call(arguments);
items.forEach(function (item) {
if (items.length === 1 && Array.isArray(items[0])) if (!self.nodes[item]) {
items = items[0]; self.nodes[item] = [];
}
items.forEach(function (item) { });
if (!self.nodes[item]) {
self.nodes[item] = []; for (let i = 1; i < items.length; i++) {
const from = items[i];
const to = items[i - 1];
self.nodes[from].push(to);
} }
});
return self;
for (let i = 1; i < items.length; i++) {
const from = items[i];
const to = items[i - 1];
self.nodes[from].push(to);
} }
return self; // Depth first search
}; // As given in http://en.wikipedia.org/wiki/Topological_sorting
sort () {
// Depth first search const self = this;
// As given in http://en.wikipedia.org/wiki/Topological_sorting const nodes = Object.keys(this.nodes);
Graph.prototype.sort = function () {
const self = this; const sorted = [];
const nodes = Object.keys(this.nodes); const marks = {};
const sorted = []; for (let i = 0; i < nodes.length; i++) {
const marks = {}; const node = nodes[i];
for (let i = 0; i < nodes.length; i++) { if (!marks[node]) {
const node = nodes[i]; visit(node);
}
if (!marks[node]) { }
visit(node);
return sorted;
function visit(node) {
if (marks[node] === 'temp')
throw new Error("There is a cycle in the graph. It is not possible to derive a topological sort.");
else if (marks[node])
return;
marks[node] = 'temp';
self.nodes[node].forEach(visit);
marks[node] = 'perm';
sorted.push(node);
} }
} }
return sorted; isEmpty () {
const nodes = Object.keys(this.nodes);
function visit(node) { return nodes.length === 0;
if (marks[node] === 'temp')
throw new Error("There is a cycle in the graph. It is not possible to derive a topological sort.");
else if (marks[node])
return;
marks[node] = 'temp';
self.nodes[node].forEach(visit);
marks[node] = 'perm';
sorted.push(node);
} }
};
Graph.prototype.isEmpty = function () {
const nodes = Object.keys(this.nodes);
return nodes.length === 0;
} }
// PATH // PATH
function resolve(parentPath, childPath) { function resolve(parentPath, childPath, remappings: string[]) {
if (remappings && remappings.length) {
for (const mapping of remappings) {
if (mapping.indexOf('=') !== -1) {
const split = mapping.split('=')
childPath = childPath.replace(split[0].trim(), split[1].trim())
}
}
}
if (_isAbsolute(childPath)) { if (_isAbsolute(childPath)) {
return childPath; return childPath;
} }

Loading…
Cancel
Save