diff --git a/.changeset/blue-cameras-roll.md b/.changeset/blue-cameras-roll.md new file mode 100644 index 0000000..6a5510a --- /dev/null +++ b/.changeset/blue-cameras-roll.md @@ -0,0 +1,5 @@ +--- +"toml-eslint-parser": patch +--- + +fix: wrong value of `bigint` in binary `TOMLIntegerValue` diff --git a/.changeset/ten-paws-decide.md b/.changeset/ten-paws-decide.md new file mode 100644 index 0000000..1399f79 --- /dev/null +++ b/.changeset/ten-paws-decide.md @@ -0,0 +1,5 @@ +--- +"toml-eslint-parser": minor +--- + +refactor for getStaticTOMLValue diff --git a/.eslintignore b/.eslintignore index fba1edf..15e77ab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,6 @@ /coverage /lib /node_modules -/tests/fixtures/**/*.json \ No newline at end of file +/tests/fixtures/**/*.json +!/.github +/toml-test-decode-last-result.json diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d78d422..3790049 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,12 +1,12 @@ # These are supported funding model platforms github: ota-meshi -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +patreon: null # Replace with a single Patreon username +open_collective: null # Replace with a single Open Collective username +ko_fi: null # Replace with a single Ko-fi username +tidelift: null # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: null # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: null # Replace with a single Liberapay username +issuehunt: null # Replace with a single IssueHunt username +otechie: null # Replace with a single Otechie username +custom: null # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/GHPages.yml b/.github/workflows/GHPages.yml index 9d6d845..b0118aa 100644 --- a/.github/workflows/GHPages.yml +++ b/.github/workflows/GHPages.yml @@ -23,22 +23,22 @@ jobs: url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - name: Install And Build - run: |+ - npm install - npm run build - cd explorer - npm install - npm run build - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Upload artifact - uses: actions/upload-pages-artifact@v2 - with: - path: ./explorer/dist - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - name: Install And Build + run: |+ + npm install + npm run build + cd explorer + npm install + npm run build + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: ./explorer/dist + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/NewOldBenchmark.yml b/.github/workflows/NewOldBenchmark.yml index 1e1bbd5..cb32311 100644 --- a/.github/workflows/NewOldBenchmark.yml +++ b/.github/workflows/NewOldBenchmark.yml @@ -10,13 +10,13 @@ jobs: benchmark: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - name: Setup - run: npm run setup - - name: Install Packages - run: npm install -f - - name: Build - run: npm run build - - name: Benchmark - run: npm run benchmark + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - name: Setup + run: npm run setup + - name: Install Packages + run: npm install -f + - name: Build + run: npm run build + - name: Benchmark + run: npm run benchmark diff --git a/.github/workflows/NodeCI.yml b/.github/workflows/NodeCI.yml index 77ef3bd..90cd11f 100644 --- a/.github/workflows/NodeCI.yml +++ b/.github/workflows/NodeCI.yml @@ -10,26 +10,26 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - - name: Install Packages - run: npm install -f - - name: Lint - run: npm run lint + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - name: Install Packages + run: npm install -f + - name: Lint + run: npm run lint test: runs-on: ubuntu-latest strategy: matrix: node-version: [12.x, 14.x, 16.x, 18.x, 20.x] steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - name: Setup - run: npm run setup - - name: Install Packages - run: npm install --legacy-peer-deps - - name: Test - run: npm test + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Setup + run: npm run setup + - name: Install Packages + run: npm install --legacy-peer-deps + - name: Test + run: npm test diff --git a/.github/workflows/toml-test.yml b/.github/workflows/toml-test.yml new file mode 100644 index 0000000..f8c5ff9 --- /dev/null +++ b/.github/workflows/toml-test.yml @@ -0,0 +1,33 @@ +name: go toml-test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v2 + - name: Setup Node.js + uses: actions/setup-node@v4 + - name: Install zsh + run: sudo apt-get update; sudo apt-get install zsh + - name: Setup + run: npm run setup + - name: Install Packages + run: npm install -f + - name: Build + run: npm run build + - name: Install toml-test + run: go install -v github.com/toml-lang/toml-test/cmd/toml-test@latest + - name: Test + run: | + export PATH="$(go env GOPATH)/bin:$PATH" + ./run-toml-test.zsh + shell: zsh {0} diff --git a/.gitignore b/.gitignore index 939833c..32068f5 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,5 @@ dist # TernJS port file .tern-port -/lib \ No newline at end of file +/lib +/toml-test-decode-last-result.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e3cdde..fdc9239 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,8 @@ "typescript", "json", "jsonc", - "yaml" + "yaml", + "github-actions-workflow", ], "typescript.validate.enable": true, "javascript.validate.enable": false, diff --git a/run-toml-test.zsh b/run-toml-test.zsh new file mode 100755 index 0000000..fd7d38a --- /dev/null +++ b/run-toml-test.zsh @@ -0,0 +1,23 @@ +#!/usr/bin/env zsh +# +# Requires toml-test from https://github.com/toml-lang/toml-test + +skip=( + # Invalid UTF-8 strings are not rejected + -skip='invalid/encoding/bad-utf8-*' + + # Certain invalid UTF-8 codepoints are not rejected + -skip='invalid/encoding/bad-codepoint' + + # Certain invalid newline codepoints are not rejected + -skip='invalid/control/rawmulti-cd' + -skip='invalid/control/multi-cr' + -skip='invalid/control/bare-cr' + + # Debug + # -run 'valid/table/with-single-quotes' +) + +e=0 +toml-test ${skip[@]} ./toml-test-decode.js || e=1 +exit $e diff --git a/src/tokenizer/tokenizer.ts b/src/tokenizer/tokenizer.ts index c9d58f2..209a79a 100644 --- a/src/tokenizer/tokenizer.ts +++ b/src/tokenizer/tokenizer.ts @@ -66,7 +66,7 @@ const RADIX_PREFIXES = { 16: "0x", 10: "", 8: "0o", - 2: "02", + 2: "0b", }; const ESCAPES_1_0: Record = { diff --git a/src/utils.ts b/src/utils.ts index a72101c..11feeb8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -15,103 +15,144 @@ import type { } from "./ast"; import { last } from "./internal-utils"; -type TOMLContentValue = - | TOMLValue["value"] - | TOMLContentValue[] - | TOMLTableValue; +type TOMLContentValue = V | TOMLContentValue[] | TOMLTableValue; -type TOMLTableValue = { - [key: string]: TOMLContentValue; +type TOMLTableValue = { + [key: string]: TOMLContentValue; }; -export function getStaticTOMLValue(node: TOMLValue): TOMLValue["value"]; -export function getStaticTOMLValue(node: TOMLArray): TOMLContentValue[]; -export function getStaticTOMLValue(node: TOMLContentNode): TOMLContentValue; -export function getStaticTOMLValue( - node: - | TOMLProgram - | TOMLTopLevelTable - | TOMLTable - | TOMLKeyValue - | TOMLInlineTable, -): TOMLTableValue; -export function getStaticTOMLValue( - node: TOMLStringValue | TOMLBare | TOMLQuoted, -): string; -export function getStaticTOMLValue(node: TOMLKey): string[]; +export type ConvertTOMLValue = { + (node: TOMLValue): V; + (node: TOMLArray): TOMLContentValue[]; + (node: TOMLContentNode): TOMLContentValue; + ( + node: + | TOMLProgram + | TOMLTopLevelTable + | TOMLTable + | TOMLKeyValue + | TOMLInlineTable, + ): TOMLTableValue; + (node: TOMLStringValue | TOMLBare | TOMLQuoted): string; + (node: TOMLKey): string[]; + (node: TOMLNode): TOMLContentValue | string | string[]; +}; +export type GetStaticTOMLValue = ConvertTOMLValue; /** * Gets the static value for the given node. */ -export function getStaticTOMLValue(node: TOMLNode): TOMLContentValue { - return resolveValue(node); -} +export const getStaticTOMLValue: GetStaticTOMLValue = generateConvertTOMLValue< + TOMLValue["value"] +>((node) => node.value); -/** - * Resolve TOML value - */ -function resolveValue( - node: TOMLNode, - baseTable?: TOMLTableValue, -): TOMLContentValue { - return resolver[node.type](node as never, baseTable); -} +/** Generates a converter to convert from a node. */ +export function generateConvertTOMLValue( + convertValue: (node: TOMLValue) => V, +): ConvertTOMLValue { + function resolveValue(node: TOMLValue, baseTable?: TOMLTableValue): V; + function resolveValue( + node: TOMLArray, + baseTable?: TOMLTableValue, + ): TOMLContentValue[]; + function resolveValue( + node: TOMLContentNode, + baseTable?: TOMLTableValue, + ): TOMLContentValue; + function resolveValue( + node: + | TOMLProgram + | TOMLTopLevelTable + | TOMLTable + | TOMLKeyValue + | TOMLInlineTable, + baseTable?: TOMLTableValue, + ): TOMLTableValue; + function resolveValue( + node: TOMLStringValue | TOMLBare | TOMLQuoted, + baseTable?: TOMLTableValue, + ): string; + function resolveValue(node: TOMLKey, baseTable?: TOMLTableValue): string[]; + function resolveValue( + node: TOMLNode, + baseTable?: TOMLTableValue, + ): TOMLContentValue | string | string[]; -const resolver = { - Program(node: TOMLProgram, baseTable: TOMLTableValue = {}) { - return resolveValue(node.body[0], baseTable); - }, - TOMLTopLevelTable(node: TOMLTopLevelTable, baseTable: TOMLTableValue = {}) { - for (const body of node.body) { - resolveValue(body, baseTable); - } - return baseTable; - }, - TOMLKeyValue(node: TOMLKeyValue, baseTable: TOMLTableValue = {}) { - const value = resolveValue(node.value); - set(baseTable, getStaticTOMLValue(node.key), value); - return baseTable; - }, - TOMLTable(node: TOMLTable, baseTable: TOMLTableValue = {}) { - const table = getTable( - baseTable, - getStaticTOMLValue(node.key), - node.kind === "array", - ); - for (const body of node.body) { - resolveValue(body, table); - } - return baseTable; - }, - TOMLArray(node: TOMLArray) { - return node.elements.map((e) => getStaticTOMLValue(e)); - }, - TOMLInlineTable(node: TOMLInlineTable) { - const table: TOMLTableValue = {}; - for (const body of node.body) { - resolveValue(body, table); - } - return table; - }, - TOMLKey(node: TOMLKey) { - return node.keys.map((key) => getStaticTOMLValue(key)); - }, - TOMLBare(node: TOMLBare) { - return node.name; - }, - TOMLQuoted(node: TOMLQuoted) { - return node.value; - }, - TOMLValue(node: TOMLValue) { - return node.value; - }, -}; + /** + * Resolve TOML value + */ + function resolveValue( + node: TOMLNode, + baseTable?: TOMLTableValue, + ): TOMLContentValue | string | string[] { + return resolver[node.type](node as never, baseTable); + } + + const resolver = { + Program(node: TOMLProgram, baseTable: TOMLTableValue = {}) { + return resolveValue(node.body[0], baseTable); + }, + TOMLTopLevelTable( + node: TOMLTopLevelTable, + baseTable: TOMLTableValue = {}, + ) { + for (const body of node.body) { + resolveValue(body, baseTable); + } + return baseTable; + }, + TOMLKeyValue(node: TOMLKeyValue, baseTable: TOMLTableValue = {}) { + const value = resolveValue(node.value); + set(baseTable, resolveValue(node.key), value); + return baseTable; + }, + TOMLTable(node: TOMLTable, baseTable: TOMLTableValue = {}) { + const table = getTable( + baseTable, + resolveValue(node.key), + node.kind === "array", + ); + for (const body of node.body) { + resolveValue(body, table); + } + return baseTable; + }, + TOMLArray(node: TOMLArray) { + return node.elements.map((e) => resolveValue(e)); + }, + TOMLInlineTable(node: TOMLInlineTable) { + const table: TOMLTableValue = {}; + for (const body of node.body) { + resolveValue(body, table); + } + return table; + }, + TOMLKey(node: TOMLKey) { + return node.keys.map((key) => resolveValue(key)); + }, + TOMLBare(node: TOMLBare) { + return node.name; + }, + TOMLQuoted(node: TOMLQuoted) { + return node.value; + }, + TOMLValue(node: TOMLValue) { + return convertValue(node); + }, + }; + + return (node: TOMLNode) => resolveValue(node) as never; +} /** * Get the table from the table. */ -function getTable(baseTable: TOMLTableValue, keys: string[], array: boolean) { - let target: TOMLTableValue = baseTable; +function getTable( + baseTable: TOMLTableValue, + keys: string[], + array: boolean, +) { + let target: TOMLTableValue = baseTable; for (let index = 0; index < keys.length - 1; index++) { const key = keys[index]; target = getNextTargetFromKey(target, key); @@ -119,20 +160,20 @@ function getTable(baseTable: TOMLTableValue, keys: string[], array: boolean) { const lastKey = last(keys)!; const lastTarget = target[lastKey]; if (lastTarget == null) { - const tableValue: TOMLTableValue = {}; + const tableValue: TOMLTableValue = {}; target[lastKey] = array ? [tableValue] : tableValue; return tableValue; } if (isValue(lastTarget)) { // Update because it is an invalid value. - const tableValue: TOMLTableValue = {}; + const tableValue: TOMLTableValue = {}; target[lastKey] = array ? [tableValue] : tableValue; return tableValue; } if (!array) { if (Array.isArray(lastTarget)) { // Update because it is an invalid value. - const tableValue: TOMLTableValue = {}; + const tableValue: TOMLTableValue = {}; target[lastKey] = tableValue; return tableValue; } @@ -141,29 +182,29 @@ function getTable(baseTable: TOMLTableValue, keys: string[], array: boolean) { if (Array.isArray(lastTarget)) { // New record - const tableValue: TOMLTableValue = {}; + const tableValue: TOMLTableValue = {}; lastTarget.push(tableValue); return tableValue; } // Update because it is an invalid value. - const tableValue: TOMLTableValue = {}; + const tableValue: TOMLTableValue = {}; target[lastKey] = [tableValue]; return tableValue; /** Get next target from key */ function getNextTargetFromKey( - currTarget: TOMLTableValue, + currTarget: TOMLTableValue, key: string, - ): TOMLTableValue { + ): TOMLTableValue { const nextTarget = currTarget[key]; if (nextTarget == null) { - const val: TOMLTableValue = {}; + const val: TOMLTableValue = {}; currTarget[key] = val; return val; } if (isValue(nextTarget)) { // Update because it is an invalid value. - const val: TOMLTableValue = {}; + const val: TOMLTableValue = {}; currTarget[key] = val; return val; } @@ -174,7 +215,7 @@ function getTable(baseTable: TOMLTableValue, keys: string[], array: boolean) { const nextElement = resultTarget[lastIndex]; if (isValue(nextElement)) { // Update because it is an invalid value. - const val: TOMLTableValue = {}; + const val: TOMLTableValue = {}; resultTarget[lastIndex] = val; return val; } @@ -187,19 +228,19 @@ function getTable(baseTable: TOMLTableValue, keys: string[], array: boolean) { /** * Set the value to the table. */ -function set(baseTable: TOMLTableValue, keys: string[], value: any) { - let target: TOMLTableValue = baseTable; +function set(baseTable: TOMLTableValue, keys: string[], value: any) { + let target: TOMLTableValue = baseTable; for (let index = 0; index < keys.length - 1; index++) { const key = keys[index]; const nextTarget = target[key]; if (nextTarget == null) { - const val: TOMLTableValue = {}; + const val: TOMLTableValue = {}; target[key] = val; target = val; } else { if (isValue(nextTarget) || Array.isArray(nextTarget)) { // Update because it is an invalid value. - const val: TOMLTableValue = {}; + const val: TOMLTableValue = {}; target[key] = val; target = val; } else { @@ -213,6 +254,6 @@ function set(baseTable: TOMLTableValue, keys: string[], value: any) { /** * Check whether the given value is a value. */ -function isValue(value: any): value is TOMLValue["value"] { +function isValue(value: TOMLContentValue): value is V { return typeof value !== "object" || value instanceof Date; } diff --git a/toml-test-decode.js b/toml-test-decode.js new file mode 100755 index 0000000..74ea598 --- /dev/null +++ b/toml-test-decode.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +"use strict"; // eslint-disable-line n/shebang -- test + +const fs = require("fs"); +/* eslint n/no-missing-require: off -- ignore */ +const toml = require("./lib/index.js"); +const { generateConvertTOMLValue } = require("./lib/utils.js"); + +/** Converter for toml-test (https://github.com/toml-lang/toml-test). */ +const convertTomlTestValue = generateConvertTOMLValue((node) => { + if (node.kind === "boolean") { + return { type: "bool", value: String(node.value) }; + } + if (node.kind === "local-date") { + return { + type: "date-local", + value: node.value.toISOString().slice(0, 10), + }; + } + if (node.kind === "local-date-time") { + return { + type: "datetime-local", + value: node.value.toISOString().slice(0, -1), + }; + } + if (node.kind === "offset-date-time") { + return { + type: "datetime", + value: node.value.toISOString(), + }; + } + if (node.kind === "local-time") { + return { + type: "time-local", + value: node.value.toISOString().slice(11, -1), + }; + } + if (node.kind === "float") { + return { + type: node.kind, + value: + node.value === Infinity + ? "+inf" + : node.value === -Infinity + ? "-inf" + : Number.isNaN(node.value) + ? "nan" + : String(node.value), + }; + } + if (node.kind === "integer") { + return { + type: node.kind, + value: String(node.bigint), + }; + } + return { type: node.kind, value: node.value }; +}); + +const ast = toml.parseTOML(fs.readFileSync(0, "utf-8")); +const result = `${JSON.stringify(convertTomlTestValue(ast), null, 2)}\n`; +process.stdout.write(result); +// fs.writeFileSync("toml-test-decode-last-result.json", result);