diff --git a/package-lock.json b/package-lock.json index 09efb2b6e..b939bdff8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,16 +11,14 @@ "dependencies": { "@nomicfoundation/slang": "0.18.3", "@solidity-parser/parser": "^0.18.0", - "semver": "^7.6.3", - "strip-comments": "^2.0.1" + "semver": "^7.6.3" }, "devDependencies": { "@babel/code-frame": "^7.26.2", "@types/jest": "^29.5.14", "@types/semver": "^7.5.8", - "@types/strip-comments": "^2.0.4", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", "c8": "^9.1.0", "cross-env": "^7.0.3", "eslint": "^9.14.0", @@ -1764,13 +1762,6 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, - "node_modules/@types/strip-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/strip-comments/-/strip-comments-2.0.4.tgz", - "integrity": "sha512-YwcQqIGy90zEHrReYrMTpZfq003Um77WayeE8UwJTHvaM9g9XR9N7GMVSnjRhhDzQYVX375JnB5P6q5kAg221g==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/yargs": { "version": "17.0.17", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.17.tgz", @@ -1787,17 +1778,17 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.12.2.tgz", - "integrity": "sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", + "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/type-utils": "8.12.2", - "@typescript-eslint/utils": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/type-utils": "8.14.0", + "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1821,16 +1812,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz", - "integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", + "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "debug": "^4.3.4" }, "engines": { @@ -1850,14 +1841,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz", - "integrity": "sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", + "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2" + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1868,14 +1859,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.12.2.tgz", - "integrity": "sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", + "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.12.2", - "@typescript-eslint/utils": "8.12.2", + "@typescript-eslint/typescript-estree": "8.14.0", + "@typescript-eslint/utils": "8.14.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1893,9 +1884,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.12.2.tgz", - "integrity": "sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", + "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", "dev": true, "license": "MIT", "engines": { @@ -1907,14 +1898,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.12.2.tgz", - "integrity": "sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", + "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/visitor-keys": "8.12.2", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/visitor-keys": "8.14.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1962,16 +1953,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.12.2.tgz", - "integrity": "sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", + "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.12.2", - "@typescript-eslint/types": "8.12.2", - "@typescript-eslint/typescript-estree": "8.12.2" + "@typescript-eslint/scope-manager": "8.14.0", + "@typescript-eslint/types": "8.14.0", + "@typescript-eslint/typescript-estree": "8.14.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1985,13 +1976,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.12.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.12.2.tgz", - "integrity": "sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", + "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.12.2", + "@typescript-eslint/types": "8.14.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -6567,15 +6558,6 @@ "node": ">=8" } }, - "node_modules/strip-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", - "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", diff --git a/package.json b/package.json index e026f0d81..6458d9963 100644 --- a/package.json +++ b/package.json @@ -86,9 +86,8 @@ "@babel/code-frame": "^7.26.2", "@types/jest": "^29.5.14", "@types/semver": "^7.5.8", - "@types/strip-comments": "^2.0.4", - "@typescript-eslint/eslint-plugin": "^8.12.2", - "@typescript-eslint/parser": "^8.12.2", + "@typescript-eslint/eslint-plugin": "^8.14.0", + "@typescript-eslint/parser": "^8.14.0", "c8": "^9.1.0", "cross-env": "^7.0.3", "eslint": "^9.14.0", @@ -111,8 +110,7 @@ "dependencies": { "@nomicfoundation/slang": "0.18.3", "@solidity-parser/parser": "^0.18.0", - "semver": "^7.6.3", - "strip-comments": "^2.0.1" + "semver": "^7.6.3" }, "peerDependencies": { "prettier": ">=3.0.0" diff --git a/src/slang-utils/create-parser.ts b/src/slang-utils/create-parser.ts index 6adbf0676..201b92a52 100644 --- a/src/slang-utils/create-parser.ts +++ b/src/slang-utils/create-parser.ts @@ -1,6 +1,7 @@ +import { VersionExpressionSets as SlangVersionExpressionSets } from '@nomicfoundation/slang/ast'; import { NonterminalKind, Query } from '@nomicfoundation/slang/cst'; import { Parser } from '@nomicfoundation/slang/parser'; -import strip from 'strip-comments'; +import prettier from 'prettier'; import { maxSatisfying, minSatisfying, @@ -9,6 +10,12 @@ import { satisfies, validRange } from 'semver'; +import slangPrint from '../slangPrinter.js'; +import { locEnd, locStart } from './loc.js'; +import { VersionExpressionSets } from '../slang-nodes/VersionExpressionSets.js'; + +import type { NonterminalNode } from '@nomicfoundation/slang/cst'; +import type { Parser as PrettierParser } from 'prettier'; const supportedVersions = Parser.supportedVersions(); @@ -25,15 +32,48 @@ const milestoneVersions = Array.from( return versions; }, []); +const bypassParse = + (parseOutput: NonterminalNode) => (): VersionExpressionSets => { + // We don't need to parse the text twice if we already have the + // NonterminalNode. + const parsed = new VersionExpressionSets( + new SlangVersionExpressionSets(parseOutput), + 0 + ); + parsed.comments = []; + return parsed; + }; + +const options = { + plugins: [ + { + languages: [{ name: 'SolidityPragma', parsers: ['slangPragma'] }], + parsers: { + slangPragma: { + astFormat: 'slang-ast', + locStart, + locEnd + } as PrettierParser + }, + printers: { ['slang-ast']: { print: slangPrint } } + } + ], + parser: 'slangPragma' +}; + +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): Parser { +export async function createParser(text: string): Promise { let inferredRanges: string[] = []; for (const version of milestoneVersions) { try { - inferredRanges = tryToCollectPragmas(text, version); + inferredRanges = await tryToCollectPragmas(text, version); break; } catch {} } @@ -57,22 +97,22 @@ export function createParser(text: string): Parser { : Parser.create(supportedVersions[supportedVersions.length - 1]); } -function tryToCollectPragmas(text: string, version: string): string[] { - const language = Parser.create(version); - const parseOutput = language.parse(NonterminalKind.SourceUnit, text); - const query = Query.parse( - '[VersionPragma @versionRanges [VersionExpressionSets]]' - ); +async function tryToCollectPragmas( + text: string, + version: string +): Promise { + const parser = Parser.create(version); + const parseOutput = parser.parse(NonterminalKind.SourceUnit, text); + const matches = parseOutput.createTreeCursor().query([query]); const ranges: string[] = []; let match; while ((match = matches.next())) { - ranges.push( - strip(match.captures.versionRanges[0].node.unparse(), { - keepProtected: true - }) + options.plugins[0].parsers.slangPragma.parse = bypassParse( + match.captures.versionRanges[0].node.asNonterminalNode()! ); + ranges.push(await prettier.format(text, options)); } if (ranges.length === 0) { diff --git a/src/slangSolidityParser.ts b/src/slangSolidityParser.ts index fdb313b3f..3f3a58287 100644 --- a/src/slangSolidityParser.ts +++ b/src/slangSolidityParser.ts @@ -12,16 +12,16 @@ import type { AstNode } from './slang-nodes/types.d.ts'; const supportedVersions = Parser.supportedVersions(); -export default function parse( +export default async function parse( text: string, options: ParserOptions -): AstNode { +): Promise { const compiler = maxSatisfying(supportedVersions, options.compiler); const parser = compiler && supportedVersions.includes(compiler) ? Parser.create(compiler) - : createParser(text); + : await createParser(text); const parseOutput = parser.parse(NonterminalKind.SourceUnit, text); printWarning( diff --git a/tests/unit/slang-utils/create-parser.test.js b/tests/unit/slang-utils/create-parser.test.js index 968106298..0963c0838 100644 --- a/tests/unit/slang-utils/create-parser.test.js +++ b/tests/unit/slang-utils/create-parser.test.js @@ -83,21 +83,31 @@ describe('inferLanguage', function () { { description: 'should use the latest version if the range is outside the supported versions', - source: `pragma solidity ^0.8.27;`, + source: `pragma solidity ^0.9.27;`, version: latestSupportedVersion + }, + { + description: 'broken by new lines, whitespaces, and comments', + source: `pragma solidity 0. + // comment 1 + 7. + /* comment 2*/ + 3;`, + version: '0.7.3' } ]; for (const { description, source, version } of fixtures) { - test(description, function () { - const parser = createParser(source); + test(description, async function () { + const parser = await createParser(source); expect(parser.version).toEqual(version); }); } test.skip('should throw an error if there are incompatible ranges', function () { - expect(() => - createParser(`pragma solidity ^0.8.0; pragma solidity 0.7.6;`) + expect( + async () => + await createParser(`pragma solidity ^0.8.0; pragma solidity 0.7.6;`) ).toThrow(); }); });