From 2c8a3c5d067fda8013efc42ac70e34d62f5c06cf Mon Sep 17 00:00:00 2001 From: Bobbie Goede Date: Mon, 18 Nov 2024 21:02:22 +0100 Subject: [PATCH] refactor: separate route parsing utilities --- src/pages.ts | 3 +- src/utils.ts | 155 ------------------------------------- src/utils/route-parsing.ts | 155 +++++++++++++++++++++++++++++++++++++ test/utils.test.ts | 3 +- 4 files changed, 159 insertions(+), 157 deletions(-) create mode 100644 src/utils/route-parsing.ts diff --git a/src/pages.ts b/src/pages.ts index 9575a4325..422440138 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -5,7 +5,8 @@ import { parse as parseSFC, compileScript } from '@vue/compiler-sfc' import { walk } from 'estree-walker' import { mkdir, readFile, writeFile } from 'node:fs/promises' import MagicString from 'magic-string' -import { formatMessage, getRoutePath, parseSegment, readFileSync } from './utils' +import { formatMessage, readFileSync } from './utils' +import { getRoutePath, parseSegment } from './utils/route-parsing' import { localizeRoutes } from './routing' import { mergeLayerPages } from './layers' import { resolve, parse as parsePath, dirname } from 'pathe' diff --git a/src/utils.ts b/src/utils.ts index 76cc3b2f9..725473ad0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,6 @@ import { parse as parsePath, resolve, relative, join } from 'pathe' import { parse as _parseCode } from '@babel/parser' import { defu } from 'defu' import { genSafeVariableName } from 'knitwork' -import { encodePath } from 'ufo' import { transform as stripType } from 'sucrase' import { isString, isRegExp, isFunction, isArray, isObject } from '@intlify/shared' import { NUXT_I18N_MODULE_ID, TS_EXTENSIONS, EXECUTABLE_EXTENSIONS, NULL_HASH } from './constants' @@ -338,142 +337,6 @@ export function stringifyObj(obj: Record): string { .join(`,`)}})` } -/** - * segment parser, forked from the below: - * - original repository url: /~https://github.com/nuxt/framework - * - code url: /~https://github.com/nuxt/framework/blob/main/packages/nuxt/src/pages/utils.ts - * - author: Nuxt Framework Team - * - license: MIT - */ - -enum SegmentParserState { - initial, - static, - dynamic, - optional, - catchall, - group -} - -enum SegmentTokenType { - static, - dynamic, - optional, - catchall, - group -} - -interface SegmentToken { - type: SegmentTokenType - value: string -} - -const PARAM_CHAR_RE = /[\w.]/ - -export function parseSegment(segment: string) { - let state: SegmentParserState = SegmentParserState.initial - let i = 0 - - let buffer = '' - const tokens: SegmentToken[] = [] - - function consumeBuffer() { - if (!buffer) { - return - } - if (state === SegmentParserState.initial) { - throw new Error('wrong state') - } - - tokens.push({ - type: - state === SegmentParserState.static - ? SegmentTokenType.static - : state === SegmentParserState.dynamic - ? SegmentTokenType.dynamic - : state === SegmentParserState.optional - ? SegmentTokenType.optional - : state === SegmentParserState.catchall - ? SegmentTokenType.catchall - : SegmentTokenType.group, - value: buffer - }) - - buffer = '' - } - - while (i < segment.length) { - const c = segment[i] - - switch (state) { - case SegmentParserState.initial: - buffer = '' - if (c === '[') { - state = SegmentParserState.dynamic - } else if (c === '(') { - state = SegmentParserState.group - } else { - i-- - state = SegmentParserState.static - } - break - - case SegmentParserState.static: - if (c === '[') { - consumeBuffer() - state = SegmentParserState.dynamic - } else if (c === '(') { - consumeBuffer() - state = SegmentParserState.group - } else { - buffer += c - } - break - - case SegmentParserState.catchall: - case SegmentParserState.dynamic: - case SegmentParserState.optional: - case SegmentParserState.group: - if (buffer === '...') { - buffer = '' - state = SegmentParserState.catchall - } - if (c === '[' && state === SegmentParserState.dynamic) { - state = SegmentParserState.optional - } - if (c === ']' && (state !== SegmentParserState.optional || segment[i - 1] === ']')) { - if (!buffer) { - throw new Error('Empty param') - } else { - consumeBuffer() - } - state = SegmentParserState.initial - } else if (c === ')' && state === SegmentParserState.group) { - if (!buffer) { - throw new Error('Empty group') - } else { - consumeBuffer() - } - state = SegmentParserState.initial - } else if (c && PARAM_CHAR_RE.test(c)) { - buffer += c - } else { - // console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`) - } - break - } - i++ - } - - if (state === SegmentParserState.dynamic) { - throw new Error(`Unfinished param "${buffer}"`) - } - - consumeBuffer() - - return tokens -} - export const getLocalePaths = (locale: LocaleObject): string[] => { return getLocaleFiles(locale).map(x => x.path) } @@ -581,24 +444,6 @@ export const mergeI18nModules = async (options: NuxtI18nOptions, nuxt: Nuxt) => } } -const COLON_RE = /:/g -export function getRoutePath(tokens: SegmentToken[]): string { - return tokens.reduce((path, token) => { - return ( - path + - (token.type === SegmentTokenType.optional - ? `:${token.value}?` - : token.type === SegmentTokenType.dynamic - ? `:${token.value}()` - : token.type === SegmentTokenType.catchall - ? `:${token.value}(.*)*` - : token.type === SegmentTokenType.group - ? '' - : encodePath(token.value).replace(COLON_RE, '\\:')) - ) - }, '/') -} - export function getHash(text: BinaryLike): string { return createHash('sha256').update(text).digest('hex').substring(0, 8) } diff --git a/src/utils/route-parsing.ts b/src/utils/route-parsing.ts new file mode 100644 index 000000000..5e4e4f9f3 --- /dev/null +++ b/src/utils/route-parsing.ts @@ -0,0 +1,155 @@ +/** + * segment parser, forked from the below: + * - original repository url: /~https://github.com/nuxt/nuxt + * - code url: /~https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/pages/utils.ts + * - author: Nuxt Framework Team + * - license: MIT + */ + +import { encodePath } from 'ufo' + +enum SegmentParserState { + initial, + static, + dynamic, + optional, + catchall, + group +} + +enum SegmentTokenType { + static, + dynamic, + optional, + catchall, + group +} + +interface SegmentToken { + type: SegmentTokenType + value: string +} + +const COLON_RE = /:/g +export function getRoutePath(tokens: SegmentToken[]): string { + return tokens.reduce((path, token) => { + return ( + path + + (token.type === SegmentTokenType.optional + ? `:${token.value}?` + : token.type === SegmentTokenType.dynamic + ? `:${token.value}()` + : token.type === SegmentTokenType.catchall + ? `:${token.value}(.*)*` + : token.type === SegmentTokenType.group + ? '' + : encodePath(token.value).replace(COLON_RE, '\\:')) + ) + }, '/') +} + +const PARAM_CHAR_RE = /[\w.]/ + +export function parseSegment(segment: string) { + let state: SegmentParserState = SegmentParserState.initial + let i = 0 + + let buffer = '' + const tokens: SegmentToken[] = [] + + function consumeBuffer() { + if (!buffer) { + return + } + if (state === SegmentParserState.initial) { + throw new Error('wrong state') + } + + tokens.push({ + type: + state === SegmentParserState.static + ? SegmentTokenType.static + : state === SegmentParserState.dynamic + ? SegmentTokenType.dynamic + : state === SegmentParserState.optional + ? SegmentTokenType.optional + : state === SegmentParserState.catchall + ? SegmentTokenType.catchall + : SegmentTokenType.group, + value: buffer + }) + + buffer = '' + } + + while (i < segment.length) { + const c = segment[i] + + switch (state) { + case SegmentParserState.initial: + buffer = '' + if (c === '[') { + state = SegmentParserState.dynamic + } else if (c === '(') { + state = SegmentParserState.group + } else { + i-- + state = SegmentParserState.static + } + break + + case SegmentParserState.static: + if (c === '[') { + consumeBuffer() + state = SegmentParserState.dynamic + } else if (c === '(') { + consumeBuffer() + state = SegmentParserState.group + } else { + buffer += c + } + break + + case SegmentParserState.catchall: + case SegmentParserState.dynamic: + case SegmentParserState.optional: + case SegmentParserState.group: + if (buffer === '...') { + buffer = '' + state = SegmentParserState.catchall + } + if (c === '[' && state === SegmentParserState.dynamic) { + state = SegmentParserState.optional + } + if (c === ']' && (state !== SegmentParserState.optional || segment[i - 1] === ']')) { + if (!buffer) { + throw new Error('Empty param') + } else { + consumeBuffer() + } + state = SegmentParserState.initial + } else if (c === ')' && state === SegmentParserState.group) { + if (!buffer) { + throw new Error('Empty group') + } else { + consumeBuffer() + } + state = SegmentParserState.initial + } else if (c && PARAM_CHAR_RE.test(c)) { + buffer += c + } else { + // console.debug(`[pages]Ignored character "${c}" while building param "${buffer}" from "segment"`) + } + break + } + i++ + } + + if (state === SegmentParserState.dynamic) { + throw new Error(`Unfinished param "${buffer}"`) + } + + consumeBuffer() + + return tokens +} diff --git a/test/utils.test.ts b/test/utils.test.ts index 1720cd5b5..cc0e0e6bc 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,4 +1,5 @@ -import { parseSegment, getRoutePath, resolveLocales } from '../src/utils' +import { resolveLocales } from '../src/utils' +import { parseSegment, getRoutePath } from '../src/utils/route-parsing' import type { LocaleObject } from '../src/types' import { vi, beforeEach, afterEach, test, expect } from 'vitest'