Merge pull request #4530 from ethereum/fix_flattener

fix flatten with remappings
pull/4531/head
yann300 9 months ago committed by GitHub
commit a309272e12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      apps/remix-ide/ci/makeMockCompiler.js
  2. 9
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  3. 7
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  4. 2
      libs/remix-solidity/src/index.ts
  5. 148
      libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities.ts
  6. 3
      libs/remix-ui/solidity-uml-gen/src/types/index.ts

@ -2,7 +2,7 @@
var fs = require('fs')
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'
const path = require('path')

@ -2,6 +2,7 @@ import React from 'react'
import {Plugin} from '@remixproject/engine'
import {customAction} from '@remixproject/plugin-api'
import {concatSourceFiles, getDependencyGraph, normalizeContractPath} from '@remix-ui/solidity-compiler'
import type {CompilerInput, CompilationSource } from '@remix-project/remix-solidity'
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 (this.triggerFlattenContract) {
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.
* @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 normalized = normalizeContractPath(filePath)
const path = `${normalized[normalized.length - 2]}${appendage}`
const ast = data.sources
const ast: { [contractName: string]: CompilationSource } = data.sources
let dependencyGraph
let sorted
let result
let sources
try {
dependencyGraph = getDependencyGraph(ast, filePath)
dependencyGraph = getDependencyGraph(ast, filePath, input.settings.remappings)
sorted = dependencyGraph.isEmpty() ? [filePath] : dependencyGraph.sort().reverse()
sources = source.sources
result = concatSourceFiles(sorted, sources)

@ -11,6 +11,7 @@ import vizRenderStringSync from '@aduh95/viz.js/sync'
import {PluginViewWrapper} from '@remix-ui/helper'
import {customAction} from '@remixproject/plugin-api'
import {ClassOptions} from 'sol2uml/lib/converterClass2Dot'
import type {CompilerInput} from '@remix-project/remix-solidity'
const parser = (window as any).SolidityParser
const _paq = (window._paq = window._paq || [])
@ -74,7 +75,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
try {
if (data.sources && Object.keys(data.sources).length > 1) {
// we should flatten first as there are multiple asts
result = await this.flattenContract(source, file, data)
result = await this.flattenContract(source, file, data, JSON.parse(input))
}
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[file].content)
this.umlClasses = convertAST2UmlClasses(ast, this.currentFile)
@ -142,8 +143,8 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
* and assigns to a local property
* @returns {Promise<string>}
*/
async flattenContract(source: any, filePath: string, data: any) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data)
async flattenContract(source: any, filePath: string, data: any, input: CompilerInput) {
const result = await this.call('contractflattener', 'flattenContract', source, filePath, data, input)
return result
}

@ -1,6 +1,6 @@
export { Compiler } from './compiler/compiler'
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 * from './compiler/types'
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 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 visited = {};
visited[target] = 1;
_traverse(graph, visited, ast, target);
_traverse(graph, visited, ast, target, remappings);
return graph;
}
@ -21,32 +24,31 @@ export function concatSourceFiles(files: any[], sources: any) {
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
currentAst = ast[name].ast
const dependencies = _getDependencies(currentAst);
for (const dependency of dependencies) {
const path = resolve(name, dependency);
const path = resolve(name, dependency, remappings);
if (path in visited) {
// continue; // fixes wrong ordering of source in flattened file
}
visited[path] = 1;
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
.filter(node => node?.nodeType === 'ImportDirective')
.map(node => node?.file);
return dependencies;
}
// TSORT
function tsort(initial?: any) {
function tsort(initial?: any): Graph {
const graph = new Graph();
if (initial) {
@ -58,78 +60,88 @@ function tsort(initial?: any) {
return graph;
}
class Graph {
nodes: { [key: string]: any}
constructor() {
this.nodes = {}
}
function Graph() {
this.nodes = {};
}
// Add sorted items to the graph
Graph.prototype.add = function () {
const self = this;
// eslint-disable-next-line prefer-rest-params
let items = [].slice.call(arguments);
if (items.length === 1 && Array.isArray(items[0]))
items = items[0];
items.forEach(function (item) {
if (!self.nodes[item]) {
self.nodes[item] = [];
// Add sorted items to the graph
add (name, path) {
const self = this;
// eslint-disable-next-line prefer-rest-params
let items = [].slice.call(arguments);
if (items.length === 1 && Array.isArray(items[0]))
items = items[0];
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);
}
});
for (let i = 1; i < items.length; i++) {
const from = items[i];
const to = items[i - 1];
self.nodes[from].push(to);
return self;
}
return self;
};
// Depth first search
// As given in http://en.wikipedia.org/wiki/Topological_sorting
Graph.prototype.sort = function () {
const self = this;
const nodes = Object.keys(this.nodes);
const sorted = [];
const marks = {};
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (!marks[node]) {
visit(node);
// Depth first search
// As given in http://en.wikipedia.org/wiki/Topological_sorting
sort () {
const self = this;
const nodes = Object.keys(this.nodes);
const sorted = [];
const marks = {};
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
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;
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);
isEmpty () {
const nodes = Object.keys(this.nodes);
return nodes.length === 0;
}
};
Graph.prototype.isEmpty = function () {
const nodes = Object.keys(this.nodes);
return nodes.length === 0;
}
// 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)) {
return childPath;
}

@ -1,5 +1,6 @@
import { ViewPlugin } from '@remixproject/engine-web'
import { customAction } from '@remixproject/plugin-api'
import type {CompilerInput} from '@remix-project/remix-solidity'
import React from 'react'
export interface ISolidityUmlGen extends ViewPlugin {
@ -17,7 +18,7 @@ export interface ISolidityUmlGen extends ViewPlugin {
updateComponent(state: any): JSX.Element
setDispatch(dispatch: React.Dispatch<any>): void
generateCustomAction(action: customAction): Promise<void>
flattenContract (source: any, filePath: string, data: any): Promise<string>
flattenContract (source: any, filePath: string, data: any, input: CompilerInput): Promise<string>
hideSpinner(): void
renderComponent (): void
triggerGenerateUml: boolean

Loading…
Cancel
Save