commit
3d19550c19
@ -0,0 +1,82 @@ |
|||||||
|
'use strict' |
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import prettier from 'prettier/standalone' |
||||||
|
import { Options } from 'prettier'; |
||||||
|
import sol from './code-format/index' |
||||||
|
import * as ts from 'prettier/parser-typescript' |
||||||
|
import * as babel from 'prettier/parser-babel' |
||||||
|
import * as espree from 'prettier/parser-espree' |
||||||
|
import path from 'path' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'codeFormatter', |
||||||
|
desciption: 'prettier plugin for Remix', |
||||||
|
methods: ['format'], |
||||||
|
events: [''], |
||||||
|
version: '0.0.1' |
||||||
|
} |
||||||
|
|
||||||
|
export class CodeFormat extends Plugin { |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
async format(file: string) { |
||||||
|
|
||||||
|
try { |
||||||
|
const content = await this.call('fileManager', 'readFile', file) |
||||||
|
if (!content) return |
||||||
|
let parserName = '' |
||||||
|
let options: Options = { |
||||||
|
} |
||||||
|
switch (path.extname(file)) { |
||||||
|
case '.sol': |
||||||
|
parserName = 'solidity-parse' |
||||||
|
break |
||||||
|
case '.ts': |
||||||
|
parserName = 'typescript' |
||||||
|
options = { |
||||||
|
...options, |
||||||
|
trailingComma: 'all', |
||||||
|
semi: false, |
||||||
|
singleQuote: true, |
||||||
|
quoteProps: 'as-needed', |
||||||
|
bracketSpacing: true, |
||||||
|
arrowParens: 'always', |
||||||
|
} |
||||||
|
break |
||||||
|
case '.js': |
||||||
|
parserName = "espree" |
||||||
|
options = { |
||||||
|
...options, |
||||||
|
semi: false, |
||||||
|
singleQuote: true, |
||||||
|
} |
||||||
|
break |
||||||
|
case '.json': |
||||||
|
parserName = 'json' |
||||||
|
break |
||||||
|
} |
||||||
|
const result = prettier.format(content, { |
||||||
|
plugins: [sol as any, ts, babel, espree], |
||||||
|
parser: parserName, |
||||||
|
...options |
||||||
|
}) |
||||||
|
await this.call('fileManager', 'writeFile', file, result) |
||||||
|
} catch (e) { |
||||||
|
// do nothing
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function getRange(index, node) { |
||||||
|
if (node.range) { |
||||||
|
return node.range[index]; |
||||||
|
} |
||||||
|
if (node.expression && node.expression.range) { |
||||||
|
return node.expression.range[index]; |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
import { handleComments, printComment } from 'prettier-plugin-solidity/src/comments'; |
||||||
|
import massageAstNode from 'prettier-plugin-solidity/src/clean.js'; |
||||||
|
import options from 'prettier-plugin-solidity/src/options.js'; |
||||||
|
import print from 'prettier-plugin-solidity/src/printer.js'; |
||||||
|
import loc from 'prettier-plugin-solidity/src/loc.js'; |
||||||
|
import { parse } from './parser' |
||||||
|
|
||||||
|
// https://prettier.io/docs/en/plugins.html#languages
|
||||||
|
// https://github.com/ikatyang/linguist-languages/blob/master/data/Solidity.json
|
||||||
|
const languages = [ |
||||||
|
{ |
||||||
|
linguistLanguageId: 237469032, |
||||||
|
name: 'Solidity', |
||||||
|
type: 'programming', |
||||||
|
color: '#AA6746', |
||||||
|
aceMode: 'text', |
||||||
|
tmScope: 'source.solidity', |
||||||
|
extensions: ['.sol'], |
||||||
|
parsers: ['solidity-parse'], |
||||||
|
vscodeLanguageIds: ['solidity'] |
||||||
|
} |
||||||
|
]; |
||||||
|
|
||||||
|
// https://prettier.io/docs/en/plugins.html#parsers
|
||||||
|
const parser = { astFormat: 'solidity-ast', parse, ...loc }; |
||||||
|
const parsers = { |
||||||
|
'solidity-parse': parser |
||||||
|
}; |
||||||
|
|
||||||
|
const canAttachComment = (node) => |
||||||
|
node.type && node.type !== 'BlockComment' && node.type !== 'LineComment'; |
||||||
|
|
||||||
|
// https://prettier.io/docs/en/plugins.html#printers
|
||||||
|
const printers = { |
||||||
|
'solidity-ast': { |
||||||
|
canAttachComment, |
||||||
|
handleComments: { |
||||||
|
ownLine: handleComments.handleOwnLineComment, |
||||||
|
endOfLine: handleComments.handleEndOfLineComment, |
||||||
|
remaining: handleComments.handleRemainingComment |
||||||
|
}, |
||||||
|
isBlockComment: handleComments.isBlockComment, |
||||||
|
massageAstNode, |
||||||
|
print, |
||||||
|
printComment |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// https://prettier.io/docs/en/plugins.html#defaultoptions
|
||||||
|
const defaultOptions = { |
||||||
|
bracketSpacing: false, |
||||||
|
tabWidth: 4 |
||||||
|
}; |
||||||
|
|
||||||
|
export default { |
||||||
|
languages, |
||||||
|
parsers, |
||||||
|
printers, |
||||||
|
options, |
||||||
|
defaultOptions |
||||||
|
}; |
@ -0,0 +1,197 @@ |
|||||||
|
// https://prettier.io/docs/en/plugins.html#parsers
|
||||||
|
import extractComments from 'solidity-comments-extractor'; |
||||||
|
// use the parser already included in the app
|
||||||
|
const parser = (window as any).SolidityParser |
||||||
|
import semver from 'semver'; |
||||||
|
|
||||||
|
const tryHug = (node, operators) => { |
||||||
|
if (node.type === 'BinaryOperation' && operators.includes(node.operator)) |
||||||
|
return { |
||||||
|
type: 'TupleExpression', |
||||||
|
components: [node], |
||||||
|
isArray: false |
||||||
|
}; |
||||||
|
return node; |
||||||
|
}; |
||||||
|
|
||||||
|
export function parse(text, _parsers, options) { |
||||||
|
const compiler = semver.coerce(options.compiler); |
||||||
|
const parsed = parser.parse(text, { loc: true, range: true }); |
||||||
|
parsed.comments = extractComments(text) |
||||||
|
|
||||||
|
parser.visit(parsed, { |
||||||
|
PragmaDirective(ctx) { |
||||||
|
// if the pragma is not for solidity we leave.
|
||||||
|
if (ctx.name !== 'solidity') return; |
||||||
|
// if the compiler option has not been provided we leave.
|
||||||
|
if (!compiler) return; |
||||||
|
// we make a check against each pragma directive in the document.
|
||||||
|
if (!semver.satisfies(compiler, ctx.value)) { |
||||||
|
// @TODO: investigate the best way to warn that would apply to
|
||||||
|
// different editors.
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn( |
||||||
|
`[prettier-solidity] The compiler option is set to '${options.compiler}', which does not satisfy 'pragma solidity ${ctx.value}'.` |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
ModifierDefinition(ctx) { |
||||||
|
if (!ctx.parameters) { |
||||||
|
ctx.parameters = []; |
||||||
|
} |
||||||
|
}, |
||||||
|
FunctionDefinition(ctx) { |
||||||
|
if (!ctx.isConstructor) { |
||||||
|
ctx.modifiers.forEach((modifier) => { |
||||||
|
if (modifier.arguments && modifier.arguments.length === 0) { |
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
modifier.arguments = null; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
ForStatement(ctx) { |
||||||
|
if (ctx.initExpression) { |
||||||
|
ctx.initExpression.omitSemicolon = true; |
||||||
|
} |
||||||
|
ctx.loopExpression.omitSemicolon = true; |
||||||
|
}, |
||||||
|
HexLiteral(ctx) { |
||||||
|
ctx.value = options.singleQuote |
||||||
|
? `hex'${ctx.value.slice(4, -1)}'` |
||||||
|
: `hex"${ctx.value.slice(4, -1)}"`; |
||||||
|
}, |
||||||
|
ElementaryTypeName(ctx) { |
||||||
|
// if the compiler is below 0.8.0 we will recognize the type 'byte' as an
|
||||||
|
// alias of 'bytes1'. Otherwise we will ignore this and enforce always
|
||||||
|
// 'bytes1'.
|
||||||
|
const pre080 = compiler && semver.satisfies(compiler, '<0.8.0'); |
||||||
|
if (!pre080 && ctx.name === 'byte') ctx.name = 'bytes1'; |
||||||
|
|
||||||
|
if (options.explicitTypes === 'always') { |
||||||
|
if (ctx.name === 'uint') ctx.name = 'uint256'; |
||||||
|
if (ctx.name === 'int') ctx.name = 'int256'; |
||||||
|
if (pre080 && ctx.name === 'byte') ctx.name = 'bytes1'; |
||||||
|
} else if (options.explicitTypes === 'never') { |
||||||
|
if (ctx.name === 'uint256') ctx.name = 'uint'; |
||||||
|
if (ctx.name === 'int256') ctx.name = 'int'; |
||||||
|
if (pre080 && ctx.name === 'bytes1') ctx.name = 'byte'; |
||||||
|
} |
||||||
|
}, |
||||||
|
BinaryOperation(ctx) { |
||||||
|
switch (ctx.operator) { |
||||||
|
case '+': |
||||||
|
case '-': |
||||||
|
ctx.left = tryHug(ctx.left, ['%']); |
||||||
|
ctx.right = tryHug(ctx.right, ['%']); |
||||||
|
break; |
||||||
|
case '*': |
||||||
|
ctx.left = tryHug(ctx.left, ['/', '%']); |
||||||
|
break; |
||||||
|
case '/': |
||||||
|
ctx.left = tryHug(ctx.left, ['*', '%']); |
||||||
|
break; |
||||||
|
case '%': |
||||||
|
ctx.left = tryHug(ctx.left, ['*', '/', '%']); |
||||||
|
break; |
||||||
|
case '**': |
||||||
|
// If the compiler has not been given as an option using we leave a**b**c.
|
||||||
|
if (!compiler) break; |
||||||
|
|
||||||
|
if (semver.satisfies(compiler, '<0.8.0')) { |
||||||
|
// If the compiler is less than 0.8.0 then a**b**c is formatted as
|
||||||
|
// (a**b)**c.
|
||||||
|
ctx.left = tryHug(ctx.left, ['**']); |
||||||
|
break; |
||||||
|
} |
||||||
|
if ( |
||||||
|
ctx.left.type === 'BinaryOperation' && |
||||||
|
ctx.left.operator === '**' |
||||||
|
) { |
||||||
|
// the parser still organizes the a**b**c as (a**b)**c so we need
|
||||||
|
// to restructure it.
|
||||||
|
ctx.right = { |
||||||
|
type: 'TupleExpression', |
||||||
|
components: [ |
||||||
|
{ |
||||||
|
type: 'BinaryOperation', |
||||||
|
operator: '**', |
||||||
|
left: ctx.left.right, |
||||||
|
right: ctx.right |
||||||
|
} |
||||||
|
], |
||||||
|
isArray: false |
||||||
|
}; |
||||||
|
ctx.left = ctx.left.left; |
||||||
|
} |
||||||
|
break; |
||||||
|
case '<<': |
||||||
|
case '>>': |
||||||
|
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']); |
||||||
|
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**']); |
||||||
|
break; |
||||||
|
case '&': |
||||||
|
ctx.left = tryHug(ctx.left, ['+', '-', '*', '/', '**', '<<', '>>']); |
||||||
|
ctx.right = tryHug(ctx.right, ['+', '-', '*', '/', '**', '<<', '>>']); |
||||||
|
break; |
||||||
|
case '|': |
||||||
|
ctx.left = tryHug(ctx.left, [ |
||||||
|
'+', |
||||||
|
'-', |
||||||
|
'*', |
||||||
|
'/', |
||||||
|
'**', |
||||||
|
'<<', |
||||||
|
'>>', |
||||||
|
'&', |
||||||
|
'^' |
||||||
|
]); |
||||||
|
ctx.right = tryHug(ctx.right, [ |
||||||
|
'+', |
||||||
|
'-', |
||||||
|
'*', |
||||||
|
'/', |
||||||
|
'**', |
||||||
|
'<<', |
||||||
|
'>>', |
||||||
|
'&', |
||||||
|
'^' |
||||||
|
]); |
||||||
|
break; |
||||||
|
case '^': |
||||||
|
ctx.left = tryHug(ctx.left, [ |
||||||
|
'+', |
||||||
|
'-', |
||||||
|
'*', |
||||||
|
'/', |
||||||
|
'**', |
||||||
|
'<<', |
||||||
|
'>>', |
||||||
|
'&' |
||||||
|
]); |
||||||
|
ctx.right = tryHug(ctx.right, [ |
||||||
|
'+', |
||||||
|
'-', |
||||||
|
'*', |
||||||
|
'/', |
||||||
|
'**', |
||||||
|
'<<', |
||||||
|
'>>', |
||||||
|
'&' |
||||||
|
]); |
||||||
|
break; |
||||||
|
case '||': |
||||||
|
ctx.left = tryHug(ctx.left, ['&&']); |
||||||
|
ctx.right = tryHug(ctx.right, ['&&']); |
||||||
|
break; |
||||||
|
case '&&': |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return parsed; |
||||||
|
} |
||||||
|
|
||||||
|
|
Loading…
Reference in new issue