Skip to content

Commit

Permalink
Merge pull request #315 from alephium/error-shift
Browse files Browse the repository at this point in the history
Reposition and add file name to compilation error
  • Loading branch information
polarker authored Feb 22, 2024
2 parents 6fa87b5 + 3b91309 commit 2326a6a
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 5 deletions.
63 changes: 58 additions & 5 deletions packages/web3/src/contract/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
typeLength,
getDefaultValue
} from '../api'
import { CompileProjectResult } from '../api/api-alephium'
import {
SignDeployContractTxParams,
SignDeployContractTxResult,
Expand Down Expand Up @@ -63,6 +64,7 @@ import * as path from 'path'
import { EventSubscribeOptions, EventSubscription, subscribeToEvents } from './events'
import { ONE_ALPH } from '../constants'
import * as blake from 'blakejs'
import { parseError } from '../utils/error'

const crypto = new WebCrypto()

Expand Down Expand Up @@ -190,6 +192,28 @@ type CodeInfo = {
warnings: string[]
}

type SourceInfoIndexes = {
sourceInfo: SourceInfo
startIndex: number
endIndex: number
}

function findSourceInfoAtLineNumber(sources: SourceInfo[], line: number): SourceInfoIndexes | undefined {
let currentLine = 0
const sourceInfosWithLine: SourceInfoIndexes[] = sources.map((source) => {
const startIndex = currentLine + 1
currentLine += source.sourceCode.split('\n').length
const endIndex = currentLine
return { sourceInfo: source, startIndex: startIndex, endIndex: endIndex }
})

const sourceInfo = sourceInfosWithLine.find((sourceInfoWithLine) => {
return line >= sourceInfoWithLine.startIndex && line <= sourceInfoWithLine.endIndex
})

return sourceInfo
}

export class ProjectArtifact {
static readonly artifactFileName = '.project.json'

Expand Down Expand Up @@ -431,6 +455,38 @@ export class Project {
return contract.artifact
}

private static async getCompileResult(
provider: NodeProvider,
compilerOptions: node.CompilerOptions,
sources: SourceInfo[]
): Promise<CompileProjectResult> {
try {
const sourceStr = sources.map((f) => f.sourceCode).join('\n')
return await provider.contracts.postContractsCompileProject({
code: sourceStr,
compilerOptions: compilerOptions
})
} catch (error) {
if (!(error instanceof Error)) {
throw error
}

const parsed = parseError(error.message)
if (!parsed) {
throw error
}

const sourceInfo = findSourceInfoAtLineNumber(sources, parsed.lineStart)
if (!sourceInfo) {
throw error
}

const shiftIndex = parsed.lineStart - sourceInfo.startIndex + 1
const newError = parsed.reformat(shiftIndex, sourceInfo.sourceInfo.contractRelativePath)
throw new Error(newError)
}
}

private static async compile(
fullNodeVersion: string,
provider: NodeProvider,
Expand All @@ -447,11 +503,8 @@ export class Project {
}
return acc
}, [])
const sourceStr = removeDuplicates.map((f) => f.sourceCode).join('\n')
const result = await provider.contracts.postContractsCompileProject({
code: sourceStr,
compilerOptions: compilerOptions
})

const result = await Project.getCompileResult(provider, compilerOptions, removeDuplicates)
const contracts = new Map<string, Compiled<Contract>>()
const scripts = new Map<string, Compiled<Script>>()
result.contracts.forEach((contractResult) => {
Expand Down
59 changes: 59 additions & 0 deletions packages/web3/src/utils/error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
Copyright 2018 - 2022 The Alephium Authors
This file is part of the alephium project.
The library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

import { parseError } from './error'

describe('error utils', function () {
it('parse and reformat error', () => {
function test(original: string, line: number, file: string, expected: string) {
const error = parseError(original)
if (error) {
expect(error.reformat(line, file)).toEqual(expected)
} else {
throw new Error(`cannoot parse error ${original}`)
}
}

const error = `-- error (256:3): Syntax error
256 | event add(a: u256, b: u256)
| ^^^^^^^^^^
| expected "}"
|-------------------------------------------------------------------------------------
|trace log: expected multicontract:1:1 / rawtxscript:2:1 / "}":3:3, found "event add("`

const expected = `nft/nft.ral (3:3): Syntax error
3 | event add(a: u256, b: u256)
| ^^^^^^^^^^
| expected "}"
|-------------------------------------------------------------------------------------
|trace log: expected multicontract:1:1 / rawtxscript:2:1 / "}":3:3, found "event add("`

const error2 = `-- error (7:3): Compilation error
7 | event Add1(b: U256, a: U256)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| These events are defined multiple times: Add1, Add2`

const expected2 = `foo.ral (123456:3): Compilation error
123456 | event Add1(b: U256, a: U256)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| These events are defined multiple times: Add1, Add2`

test(error, 3, 'nft/nft.ral', expected)
test(error2, 123456, 'foo.ral', expected2)
})
})
77 changes: 77 additions & 0 deletions packages/web3/src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2018 - 2022 The Alephium Authors
This file is part of the alephium project.
The library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the library. If not, see <http://www.gnu.org/licenses/>.
*/

class CompilationError {
constructor(
public lineStart: number,
public column: number,
public errorType: string,
public line: number,
public codeLine: string,
public errorIndicator: string,
public message: string,
public additionalLine1?: string,
public additionalLine2?: string
) {}

reformat(line: number, file: string): string {
const spaces = `${line}`.replace(/\d/g, ' ')
const newError = `${file} (${line}:${this.column}): ${this.errorType}
${line} |${this.codeLine}
${spaces} |${this.errorIndicator}
${spaces} |${this.message}`

if (this.additionalLine1 && this.additionalLine2) {
return `${newError}\n${spaces} |${this.additionalLine1}\n${spaces} |${this.additionalLine2}`
} else {
return newError
}
}
}

const errorRegex = /error \((\d+):(\d+)\):\s*(.*)\n\s*(\d+)\s*\|(.*)\n.*\|(.*)\n\s*\|(.*)(?:\n\s*\|(.*)\n\s*\|(.*))?/

export function parseError(error: string): CompilationError | undefined {
const match = error.match(errorRegex)

if (match) {
const lineStart = parseInt(match[1])
const column = parseInt(match[2])
const errorType = match[3]
const line = parseInt(match[4])
const codeLine = match[5]
const errorIndicator = match[6]
const message = match[7]
const additionalLine1 = match[8]
const additionalLine2 = match[9]

return new CompilationError(
lineStart,
column,
errorType,
line,
codeLine,
errorIndicator,
message,
additionalLine1,
additionalLine2
)
} else {
undefined
}
}

0 comments on commit 2326a6a

Please sign in to comment.