diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index 0d1d1c1d..99946b16 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -11,7 +11,9 @@ import { } from 'semver'; import { VersionExpressionSets } from '../slang-nodes/VersionExpressionSets.js'; +import type { ParseOutput } from '@nomicfoundation/slang/parser'; import type { ParserOptions } from 'prettier'; +import type { AstNode } from '../slang-nodes/types.js'; const supportedVersions = Parser.supportedVersions(); @@ -32,17 +34,36 @@ const query = Query.parse( '[VersionPragma @versionRanges [VersionExpressionSets]]' ); -// TODO if we ended up selecting the same version that the pragmas were parsed with, -// should we be able to reuse/just return the already parsed CST, instead of -// returning a Parser and forcing user to parse it again? -export function createParser(text: string, options: ParserOptions): Parser { +export function createParser( + text: string, + options: ParserOptions +): [Parser, ParseOutput] { + const compiler = maxSatisfying(supportedVersions, options.compiler); + let parser: Parser; + if (compiler) { + parser = Parser.create(compiler); + return [parser, parser.parse(NonterminalKind.SourceUnit, text)]; + } + + parser = Parser.create(milestoneVersions[0]); + let parseOutput; let inferredRanges: string[] = []; - for (const version of milestoneVersions) { - try { - inferredRanges = tryToCollectPragmas(text, version); - break; - } catch {} + try { + parseOutput = parser.parse(NonterminalKind.SourceUnit, text); + inferredRanges = tryToCollectPragmas(parseOutput, parser); + } catch { + for (let i = 1; i <= milestoneVersions.length; i += 1) { + try { + const version = milestoneVersions[i]; + parser = Parser.create(version); + parseOutput = parser.parse(NonterminalKind.SourceUnit, text); + inferredRanges = tryToCollectPragmas(parseOutput, parser); + break; + } catch { + continue; + } + } } const satisfyingVersions = inferredRanges.reduce( @@ -59,14 +80,24 @@ export function createParser(text: string, options: ParserOptions): Parser { supportedVersions ); - return satisfyingVersions.length > 0 - ? Parser.create(satisfyingVersions[satisfyingVersions.length - 1]) - : Parser.create(supportedVersions[supportedVersions.length - 1]); + const inferredVersion = + satisfyingVersions.length > 0 + ? satisfyingVersions[satisfyingVersions.length - 1] + : supportedVersions[supportedVersions.length - 1]; + + if (inferredVersion !== parser.version) { + parser = Parser.create(inferredVersion); + parseOutput = parser.parse(NonterminalKind.SourceUnit, text); + } + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return [parser, parseOutput!]; } -function tryToCollectPragmas(text: string, version: string): string[] { - const language = Parser.create(version); - const parseOutput = language.parse(NonterminalKind.SourceUnit, text); +function tryToCollectPragmas( + parseOutput: ParseOutput, + parser: Parser +): string[] { const matches = parseOutput.createTreeCursor().query([query]); const ranges: string[] = []; @@ -87,11 +118,11 @@ function tryToCollectPragmas(text: string, version: string): string[] { if (ranges.length === 0) { // If we didn't find pragmas but succeeded parsing the source we keep it. if (parseOutput.isValid()) { - return [version]; + return [parser.version]; } // Otherwise we throw. throw new Error( - `Using version ${version} did not find any pragma statement and does not parse without errors.` + `Using version ${parser.version} did not find any pragma statement and does not parse without errors.` ); } diff --git a/src/slangSolidityParser.ts b/src/slangSolidityParser.ts index 342d3a9f..ea5d7486 100644 --- a/src/slangSolidityParser.ts +++ b/src/slangSolidityParser.ts @@ -1,35 +1,17 @@ // https://prettier.io/docs/en/plugins.html#parsers -import { Parser } from '@nomicfoundation/slang/parser'; -import { NonterminalKind } from '@nomicfoundation/slang/cst'; import { SourceUnit as SlangSourceUnit } from '@nomicfoundation/slang/ast'; -import { maxSatisfying } from 'semver'; import { clearOffsets } from './slang-utils/metadata.js'; import { createParser } from './slang-utils/create-parser.js'; -import { printWarning } from './slang-utils/print-warning.js'; import { SourceUnit } from './slang-nodes/SourceUnit.js'; import type { ParserOptions } from 'prettier'; import type { AstNode } from './slang-nodes/types.d.ts'; -const supportedVersions = Parser.supportedVersions(); - export default function parse( text: string, options: ParserOptions ): AstNode { - const compiler = maxSatisfying(supportedVersions, options.compiler); - - const parser = - compiler && supportedVersions.includes(compiler) - ? Parser.create(compiler) - : createParser(text, options); - - const parseOutput = parser.parse(NonterminalKind.SourceUnit, text); - printWarning( - compiler - ? `Using version ${parser.version} based on the compiler option provided.` - : `Inferred version ${parser.version} based on the pragma statements in the code.` - ); + const [parser, parseOutput] = createParser(text, options); if (parseOutput.isValid()) { // We update the compiler version by the inferred one. diff --git a/tests/unit/slang-utils/create-parser.test.js b/tests/unit/slang-utils/create-parser.test.js index 5e4c0f07..6bacab62 100644 --- a/tests/unit/slang-utils/create-parser.test.js +++ b/tests/unit/slang-utils/create-parser.test.js @@ -91,7 +91,7 @@ describe('inferLanguage', function () { for (const { description, source, version } of fixtures) { test(description, function () { - const parser = createParser(source, options); + const [parser] = createParser(source, options); expect(parser.version).toEqual(version); }); }