Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide compilation warnings #1897

Merged
merged 1 commit into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/contract/compiler/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ export type Aci = Array<{
};
}>;

export type CompileResult = Promise<{
bytecode: Encoded.ContractBytearray;
aci: Aci;
warnings: Array<{
message: string;
pos: {
file?: string;
line: number;
col: number;
};
}>;
}>;

/**
* A base class for all compiler implementations
*/
Expand All @@ -29,10 +42,7 @@ export default abstract class CompilerBase {
* @param path - Path to contract source code
* @returns ACI and bytecode
*/
abstract compile(path: string): Promise<{
bytecode: Encoded.ContractBytearray;
aci: Aci;
}>;
abstract compile(path: string): CompileResult;

/**
* Compile contract by contract's source code
Expand All @@ -50,10 +60,7 @@ export default abstract class CompilerBase {
abstract compileBySourceCode(
sourceCode: string,
fileSystem?: Record<string, string>,
): Promise<{
bytecode: Encoded.ContractBytearray;
aci: Aci;
}>;
): CompileResult;

/**
* Generate contract's ACI by contract's path
Expand Down
47 changes: 31 additions & 16 deletions src/contract/compiler/Cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { tmpdir } from 'os';
import { resolve, dirname, basename } from 'path';
import { mkdir, writeFile, rm } from 'fs/promises';
import { fileURLToPath } from 'url';
import CompilerBase, { Aci } from './Base';
import CompilerBase, { Aci, CompileResult } from './Base';
import { Encoded } from '../../utils/encoder';
import { CompilerError, InternalError, UnsupportedVersionError } from '../../utils/errors';
import semverSatisfies from '../../utils/semver-satisfies';
Expand Down Expand Up @@ -45,16 +45,21 @@ export default class CompilerCli extends CompilerBase {
}
}

async #run(...parameters: string[]): Promise<string> {
async #runWithStderr(...parameters: string[]): Promise<{ stderr: string; stdout: string }> {
return new Promise((pResolve, pReject) => {
execFile('escript', [this.#path, ...parameters], (error, stdout, stderr) => {
if (error != null) pReject(error);
else if (stderr !== '') pReject(new CompilerError(stderr));
else pResolve(stdout);
else pResolve({ stdout, stderr });
});
});
}

async #run(...parameters: string[]): Promise<string> {
const { stderr, stdout } = await this.#runWithStderr(...parameters);
if (stderr !== '') throw new CompilerError(stderr);
return stdout;
}

static async #saveContractToTmpDir(
sourceCode: string,
fileSystem: Record<string, string> = {},
Expand All @@ -73,30 +78,40 @@ export default class CompilerCli extends CompilerBase {
return sourceCodePath;
}

async compile(path: string): Promise<{
bytecode: Encoded.ContractBytearray;
aci: Aci;
}> {
async compile(path: string): CompileResult {
await this.#ensureCompatibleVersion;
try {
const [bytecode, aci] = await Promise.all([
this.#run(path, '--no_warning', 'all'),
this.#run('--create_json_aci', path).then((res) => JSON.parse(res)),
const [compileRes, aci] = await Promise.all([
this.#runWithStderr(path),
this.generateAci(path),
]);
return {
bytecode: bytecode.trimEnd() as Encoded.ContractBytearray,
bytecode: compileRes.stdout.trimEnd() as Encoded.ContractBytearray,
aci,
warnings: compileRes.stderr.split('Warning in ').slice(1).map((warning) => {
const reg = /^'(.+)' at line (\d+), col (\d+):\n(.+)$/s;
const match = warning.match(reg);
if (match == null) throw new InternalError(`Can't parse compiler output: "${warning}"`);
return {
message: match[4].trimEnd(),
pos: {
...match[1] !== path && { file: match[1] },
line: +match[2],
col: +match[3],
},
};
}),
};
} catch (error) {
ensureError(error);
throw new CompilerError(error.message);
}
}

async compileBySourceCode(sourceCode: string, fileSystem?: Record<string, string>): Promise<{
bytecode: Encoded.ContractBytearray;
aci: Aci;
}> {
async compileBySourceCode(
sourceCode: string,
fileSystem?: Record<string, string>,
): CompileResult {
const tmp = await CompilerCli.#saveContractToTmpDir(sourceCode, fileSystem);
try {
return await this.compile(tmp);
Expand Down
13 changes: 8 additions & 5 deletions src/contract/compiler/Http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
CompilerError as CompilerErrorApi,
} from '../../apis/compiler';
import { genErrorFormatterPolicy, genVersionCheckPolicy } from '../../utils/autorest';
import CompilerBase, { Aci } from './Base';
import CompilerBase, { Aci, CompileResult } from './Base';
import { Encoded } from '../../utils/encoder';
import { CompilerError, NotImplementedError } from '../../utils/errors';

Expand Down Expand Up @@ -63,11 +63,14 @@ export default class CompilerHttp extends CompilerBase {
async compileBySourceCode(
sourceCode: string,
fileSystem?: Record<string, string>,
): Promise<{ bytecode: Encoded.ContractBytearray; aci: Aci }> {
): CompileResult {
try {
const res = await this.api.compileContract({ code: sourceCode, options: { fileSystem } });
const cmpOut = await this.api.compileContract({ code: sourceCode, options: { fileSystem } });
cmpOut.warnings ??= []; // TODO: remove after requiring http compiler above or equal to 8.0.0
const warnings = cmpOut.warnings.map(({ type, ...warning }) => warning);
const res = { ...cmpOut, warnings };
// TODO: should be fixed when the compiledAci interface gets updated
return res as { bytecode: Encoded.ContractBytearray; aci: Aci };
return res as Awaited<CompileResult>;
} catch (error) {
if (error instanceof RestError && error.statusCode === 400) {
throw new CompilerError(error.message);
Expand All @@ -77,7 +80,7 @@ export default class CompilerHttp extends CompilerBase {
}

// eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars
async compile(path: string): Promise<{ bytecode: Encoded.ContractBytearray; aci: Aci }> {
async compile(path: string): CompileResult {
throw new NotImplementedError('File system access, use CompilerHttpNode instead');
}

Expand Down
4 changes: 2 additions & 2 deletions src/contract/compiler/HttpNode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readFile } from 'fs/promises';
import HttpBrowser from './Http';
import { Aci } from './Base';
import { Aci, CompileResult } from './Base';
import { Encoded } from '../../utils/encoder';
import getFileSystem from './getFileSystem';

Expand All @@ -12,7 +12,7 @@ import getFileSystem from './getFileSystem';
* @example CompilerHttpNode('COMPILER_URL')
*/
export default class CompilerHttpNode extends HttpBrowser {
override async compile(path: string): Promise<{ bytecode: Encoded.ContractBytearray; aci: Aci }> {
override async compile(path: string): CompileResult {
const fileSystem = await getFileSystem(path);
const sourceCode = await readFile(path, 'utf8');
return this.compileBySourceCode(sourceCode, fileSystem);
Expand Down
35 changes: 33 additions & 2 deletions test/integration/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,18 @@ function testCompiler(compiler: CompilerBase, isAesophia7: boolean): void {
});

it('compiles and generates aci by path', async () => {
const { bytecode, aci } = await compiler.compile(inclSourceCodePath);
const { bytecode, aci, warnings } = await compiler.compile(inclSourceCodePath);
expect(bytecode).to.equal(inclBytecode);
expect(aci).to.eql(inclAci);
expect(warnings).to.eql([]);
});

it('compiles and generates aci by source code', async () => {
const { bytecode, aci } = await compiler.compileBySourceCode(inclSourceCode, inclFileSystem);
const { bytecode, aci, warnings } = await compiler
.compileBySourceCode(inclSourceCode, inclFileSystem);
expect(bytecode).to.equal(inclBytecode);
expect(aci).to.eql(inclAci);
expect(warnings).to.eql([]);
});

it('throws clear exception if compile broken contract', async () => {
Expand All @@ -97,6 +100,34 @@ function testCompiler(compiler: CompilerBase, isAesophia7: boolean): void {
);
});

it('returns warnings', async () => {
const { warnings } = await compiler.compileBySourceCode(
'include "./lib/Library.aes"\n'
+ '\n'
+ 'main contract Foo =\n'
+ ' entrypoint getArg(x: int) =\n'
+ ' let t = 42\n'
+ ' x\n',
{
'./lib/Library.aes': ''
+ 'contract Library =\n'
+ ' entrypoint getArg() =\n'
+ ' 1 / 0\n',
},
);
if (isAesophia7 && compiler instanceof CompilerHttpNode) {
expect(warnings).to.eql([]);
return;
}
expect(warnings).to.eql([{
message: 'The variable `t` is defined but never used.',
pos: { col: 9, line: 5 },
}, {
message: 'Division by zero.',
pos: { file: './lib/Library.aes', col: 5, line: 3 },
}]);
});

it('generates aci by path', async () => {
const aci = await compiler.generateAci(interfaceSourceCodePath);
expect(aci).to.eql(interfaceAci);
Expand Down
2 changes: 1 addition & 1 deletion tooling/autorest/compiler-prepare.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';

const swaggerUrl = 'https://raw.githubusercontent.com/aeternity/aesophia_http/v7.4.0/config/swagger.yaml';
const swaggerUrl = 'https://raw.githubusercontent.com/aeternity/aesophia_http/v8.0.0-rc1/config/swagger.yaml';

const response = await fetch(swaggerUrl);
console.assert(response.status === 200, 'Invalid response code', response.status);
Expand Down
Loading