Skip to content

Commit

Permalink
refactor: separate route parsing utilities (#3236)
Browse files Browse the repository at this point in the history
  • Loading branch information
BobbieGoede authored Nov 19, 2024
1 parent 1dfb809 commit ea7aa14
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 157 deletions.
3 changes: 2 additions & 1 deletion src/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
155 changes: 0 additions & 155 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -338,142 +337,6 @@ export function stringifyObj(obj: Record<string, any>): 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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
155 changes: 155 additions & 0 deletions src/utils/route-parsing.ts
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 2 additions & 1 deletion test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down

0 comments on commit ea7aa14

Please sign in to comment.