Skip to content
Closed
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
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,26 @@ The following is the default configuration internally used by this plugin.
"plugins": ["prettier-plugin-solidity"],
"overrides": [
{
"files": "*.sol",
"files": ["*.sol", "*.yul"],
"options": {
"parser": "slang",
"printWidth": 80,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": false,
}
},
{
"files": "*.sol",
"options": {
"parser": "slang"
}
},
{
"files": "*.yul",
"options": {
"parser": "slang-yul"
}
}
]
}
Expand Down
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const slangParserId = 'slang';
export const slangYulParserId = 'slang-yul';
export const antlrParserId = 'antlr';
export const slangAstId = 'slang-ast';
export const antlrAstId = 'antlr-ast';
29 changes: 24 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ import options from './options.js';
import antlrParse from './parser.js';
import antlrPrint from './printer.js';
import slangParse from './slangSolidityParser.js';
import yulParse from './slangYulParser.js';
import slangPrint from './slangPrinter.js';
import { isBlockComment, isComment } from './slang-utils/is-comment.js';
import { locEnd, locStart } from './slang-utils/loc.js';
import {
antlrAstId,
antlrParserId,
slangAstId,
slangParserId,
slangYulParserId
} from './constants.js';

import type {
Parser,
Expand All @@ -18,11 +26,6 @@ import type {
} from 'prettier';
import type { AstNode } from './slang-nodes/types.d.ts';

const slangParserId = 'slang';
const antlrParserId = 'antlr';
const slangAstId = 'slang-ast';
const antlrAstId = 'antlr-ast';

// https://prettier.io/docs/en/plugins.html#languages
// https://github.com/github-linguist/linguist/blob/master/lib/linguist/languages.yml
const languages: SupportLanguage[] = [
Expand All @@ -34,6 +37,15 @@ const languages: SupportLanguage[] = [
extensions: ['.sol'],
parsers: [slangParserId, antlrParserId],
vscodeLanguageIds: ['solidity']
},
{
linguistLanguageId: 237469033,
name: 'Yul',
aceMode: 'text',
tmScope: 'source.yul',
extensions: ['.yul'],
parsers: [slangYulParserId],
vscodeLanguageIds: ['yul']
}
];

Expand All @@ -45,9 +57,16 @@ const slangParser: Parser<AstNode> = {
locStart,
locEnd
};
const yulParser: Parser<AstNode> = {
astFormat: slangAstId,
parse: yulParse,
locStart,
locEnd
};

const parsers = {
[slangParserId]: slangParser,
[slangYulParserId]: yulParser,
[antlrParserId]: antlrParser
};

Expand Down
29 changes: 21 additions & 8 deletions src/slang-utils/create-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,34 @@ import { NonterminalKind } from '@nomicfoundation/slang/cst';
import { Parser } from '@nomicfoundation/slang/parser';
import { LanguageFacts } from '@nomicfoundation/slang/utils';
import { maxSatisfying } from 'semver';
import { slangParserId, slangYulParserId } from '../constants.js';

import type { ParseOutput } from '@nomicfoundation/slang/parser';
import type { ParserOptions } from 'prettier';
import type { AstNode } from '../slang-nodes/types.d.ts';

const supportedVersions = LanguageFacts.allVersions();
const supportedLength = supportedVersions.length;
const rootKindMap = new Map<ParserOptions<AstNode>['parser'], NonterminalKind>([
[slangParserId, NonterminalKind.SourceUnit],
[slangYulParserId, NonterminalKind.YulBlock]
]);

function parserAndOutput(
text: string,
version: string
version: string,
{ parser: optionsParser }: ParserOptions<AstNode>
): { parser: Parser; parseOutput: ParseOutput } {
const rootKind = rootKindMap.get(optionsParser);

if (rootKind === undefined) {
throw new Error(
`Parser '${optionsParser as string}' is not supported for Language Inference.`
);
}

const parser = Parser.create(version);
return {
parser,
parseOutput: parser.parseNonterminal(NonterminalKind.SourceUnit, text)
};
return { parser, parseOutput: parser.parseNonterminal(rootKind, text) };
}

function createError(
Expand All @@ -36,7 +47,7 @@ export function createParser(
): { parser: Parser; parseOutput: ParseOutput } {
const compiler = maxSatisfying(supportedVersions, options.compiler);
if (compiler) {
const result = parserAndOutput(text, compiler);
const result = parserAndOutput(text, compiler, options);

if (!result.parseOutput.isValid())
throw createError(
Expand All @@ -55,7 +66,8 @@ export function createParser(
if (inferredLength === 0 || inferredLength === supportedLength) {
const result = parserAndOutput(
text,
supportedVersions[supportedLength - 1]
supportedVersions[supportedLength - 1],
options
);

if (!result.parseOutput.isValid())
Expand All @@ -70,7 +82,8 @@ export function createParser(

const result = parserAndOutput(
text,
inferredRanges[inferredRanges.length - 1]
inferredRanges[inferredLength - 1],
options
);

if (!result.parseOutput.isValid())
Expand Down
28 changes: 28 additions & 0 deletions src/slangYulParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// https://prettier.io/docs/en/plugins.html#parsers
import { YulBlock as SlangYulBlock } from '@nomicfoundation/slang/ast';
import { createParser } from './slang-utils/create-parser.js';
import { YulBlock } from './slang-nodes/YulBlock.js';

import type { ParserOptions } from 'prettier';
import type { AstNode, Comment } from './slang-nodes/types.d.ts';

export default function parse(
text: string,
options: ParserOptions<AstNode>
): AstNode {
const { parser, parseOutput } = createParser(text, options);

// We update the compiler version by the inferred one.
options.compiler = parser.languageVersion;
const comments: Comment[] = [];
const parsed = new YulBlock(
new SlangYulBlock(parseOutput.tree.asNonterminalNode()),
{ offsets: new Map<number, number>(), comments },
options
);

// Because of comments being extracted like a Russian doll, the order needs
// to be fixed at the end.
parsed.comments = comments.sort((a, b) => a.loc.start - b.loc.start);
return parsed;
}
74 changes: 74 additions & 0 deletions tests/format/Yul/__snapshots__/format.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`example1.yul format 1`] = `
====================================options=====================================
parsers: ["slang-yul"]
printWidth: 80
| printWidth
=====================================input======================================
{
function power(base, exponent) -> result
{
switch exponent
case 0 { result := 1 }
case 1 { result := base }
default
{
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 { result := mul(base, result) }
}
}
}
=====================================output=====================================
{
function power(base, exponent) -> result {
switch exponent
case 0 {
result := 1
}
case 1 {
result := base
}
default {
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 {
result := mul(base, result)
}
}
}
}
================================================================================
`;

exports[`example2.yul format 1`] = `
====================================options=====================================
parsers: ["slang-yul"]
printWidth: 80
| printWidth
=====================================input======================================
{
function power(base, exponent) -> result
{
result := 1 for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{
result := mul(result, base)
}
}
}
=====================================output=====================================
{
function power(base, exponent) -> result {
result := 1
for {
let i := 0
} lt(i, exponent) {
i := add(i, 1)
} {
result := mul(result, base)
}
}
}
================================================================================
`;
14 changes: 14 additions & 0 deletions tests/format/Yul/example1.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
function power(base, exponent) -> result
{
switch exponent
case 0 { result := 1 }
case 1 { result := base }
default
{
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 { result := mul(base, result) }
}
}
}
9 changes: 9 additions & 0 deletions tests/format/Yul/example2.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
function power(base, exponent) -> result
{
result := 1 for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{
result := mul(result, base)
}
}
}
1 change: 1 addition & 0 deletions tests/format/Yul/format.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runFormatTest(import.meta, ['slang-yul']);
12 changes: 9 additions & 3 deletions tests/unit/slang-utils/create-parser.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { LanguageFacts } from '@nomicfoundation/slang/utils';
import { createParser } from '../../../src/slang-utils/create-parser.js';
import { slangParserId } from '../../../src/constants.js';

describe('inferLanguage', function () {
const latestSupportedVersion = LanguageFacts.latestVersion();
const options = { filepath: 'test.sol' };
const options = { parser: slangParserId };

const fixtures = [
{
Expand Down Expand Up @@ -97,22 +98,27 @@ describe('inferLanguage', function () {

test('should use compiler option if given', function () {
let { parser } = createParser(`pragma solidity ^0.8.0;`, {
...options,
compiler: '0.8.20'
});
expect(parser.languageVersion).toEqual('0.8.20');

({ parser } = createParser(`pragma solidity ^0.8.0;`, {
...options,
compiler: '0.8.2'
}));
expect(parser.languageVersion).toEqual('0.8.2');

({ parser } = createParser(`pragma solidity ^0.7.0;`, {}));
({ parser } = createParser(`pragma solidity ^0.7.0;`, options));
expect(parser.languageVersion).toEqual('0.7.6');
});

test('should throw if compiler option does not match the syntax', function () {
expect(() =>
createParser(`contract Foo {byte bar;}`, { compiler: '0.8.0' })
createParser(`contract Foo {byte bar;}`, {
...options,
compiler: '0.8.0'
})
).toThrow(
'Based on the compiler option provided, we inferred your code to be using Solidity version'
);
Expand Down