Skip to content

Commit

Permalink
refactor: extract some logics
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jan 13, 2024
1 parent da836b0 commit e59af90
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 102 deletions.
107 changes: 6 additions & 101 deletions packages/twoslash/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,16 @@ import { createFSBackedSystem, createSystem, createVirtualTypeScriptEnvironment
import { objectHash } from 'ohash'
import { TwoslashError } from './error'
import type { CreateTwoSlashOptions, NodeError, NodeWithoutPosition, ParsedFlagNotation, Position, Range, TwoSlashExecuteOptions, TwoSlashInstance, TwoSlashOptions, TwoSlashReturn, TwoSlashReturnMeta } from './types'

Check failure on line 5 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'ParsedFlagNotation' is defined but never used
import { areRangesIntersecting, createPositionConverter, getExtension, getIdentifierTextSpans, isInRange, isInRanges, parseFlag, removeCodeRanges, resolveNodePositions, splitFiles, typesToExtension } from './utils'
import { areRangesIntersecting, createPositionConverter, findCutNotations, findFlagNotations, findQueryMarkers, getExtension, getIdentifierTextSpans, isInRange, isInRanges, parseFlag, removeCodeRanges, resolveNodePositions, splitFiles, typesToExtension } from './utils'

Check failure on line 6 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'parseFlag' is defined but never used
import { validateCodeForErrors } from './validation'
import { defaultCompilerOptions, defaultHandbookOptions } from './defaults'
import type { CompilerOptionDeclaration } from './types/internal'
import { reAnnonateMarkers, reCutAfter, reCutBefore, reCutEnd, reCutStart } from './regexp'

Check failure on line 10 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'reAnnonateMarkers' is defined but never used

Check failure on line 10 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'reCutAfter' is defined but never used

Check failure on line 10 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'reCutBefore' is defined but never used

Check failure on line 10 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'reCutEnd' is defined but never used

Check failure on line 10 in packages/twoslash/src/core.ts

View workflow job for this annotation

GitHub Actions / lint

'reCutStart' is defined but never used

export * from './public'

type TS = typeof import('typescript')

// TODO: Make them configurable maybe
const reConfigBoolean = /^\/\/\s?@(\w+)$/mg
const reConfigValue = /^\/\/\s?@(\w+):\s?(.+)$/mg
const reAnnonateMarkers = /^\s*\/\/\s*\^(\?|\||\^+)( .*)?$/mg

const reCutBefore = /^\/\/\s?---cut(-before)?---$/mg
const reCutAfter = /^\/\/\s?---cut-after---$/mg
const reCutStart = /^\/\/\s?---cut-start---$/mg
const reCutEnd = /^\/\/\s?---cut-end---$/mg

/**
* Create a TwoSlash instance with cached TS environments
*/
Expand Down Expand Up @@ -132,34 +123,10 @@ export function createTwoSlasher(createOptions: CreateTwoSlashOptions = {}): Two
const ls = env.languageService
const pc = createPositionConverter(code)

// #region extract cuts
meta.removals.push(...extractCuts(code))
// #endregion

// #region extract markers
if (code.includes('//')) {
Array.from(code.matchAll(reAnnonateMarkers)).forEach((match) => {
const type = match[1] as '?' | '|' | '^^'
const index = match.index!
meta.removals.push([index, index + match[0].length + 1])
const markerIndex = match[0].indexOf('^')
const targetIndex = pc.getIndexOfLineAbove(index + markerIndex)
if (type === '?') {
meta.positionQueries.push(targetIndex)
}
else if (type === '|') {
meta.positionCompletions.push(targetIndex)
}
else {
const markerLength = match[0].lastIndexOf('^') - markerIndex + 1
meta.positionHighlights.push([
targetIndex,
targetIndex + markerLength,
])
}
})
}
// #endregion
// extract cuts
meta.removals.push(...findCutNotations(code))
// extract markers
findQueryMarkers(code, meta, pc.getIndexOfLineAbove)

const supportedFileTyes = ['js', 'jsx', 'ts', 'tsx']
meta.virtualFiles = splitFiles(code, defaultFilename, fsRoot)
Expand Down Expand Up @@ -463,44 +430,6 @@ export function createTwoSlasher(createOptions: CreateTwoSlashOptions = {}): Two
return twoslasher
}

function extractCuts(code: string) {
const removals: Range[] = []

const cutBefore = [...code.matchAll(reCutBefore)]
const cutAfter = [...code.matchAll(reCutAfter)]
const cutStart = [...code.matchAll(reCutStart)]
const cutEnd = [...code.matchAll(reCutEnd)]

if (cutBefore.length) {
const last = cutBefore[cutBefore.length - 1]
removals.push([0, last.index! + last[0].length + 1])
}
if (cutAfter.length) {
const first = cutAfter[0]
removals.push([first.index!, code.length])
}
if (cutStart.length !== cutEnd.length) {
throw new TwoslashError(
`Mismatched cut markers`,
`You have ${cutStart.length} cut-starts and ${cutEnd.length} cut-ends`,
`Make sure you have a matching pair for each.`,
)
}
for (let i = 0; i < cutStart.length; i++) {
const start = cutStart[i]
const end = cutEnd[i]
if (start.index! > end.index!) {
throw new TwoslashError(
`Mismatched cut markers`,
`You have a cut-start at ${start.index} which is after the cut-end at ${end.index}`,
`Make sure you have a matching pair for each.`,
)
}
removals.push([start.index!, end.index! + end[0].length + 1])
}
return removals
}

/**
* Run TwoSlash on a string of code
*
Expand All @@ -512,27 +441,3 @@ export function twoslasher(code: string, lang?: string, opts?: Partial<TwoSlashO
cache: false,
})(code, lang)
}

function findFlagNotations(code: string, customTags: string[], tsOptionDeclarations: CompilerOptionDeclaration[]) {
const flagNotations: ParsedFlagNotation[] = []

// #extract compiler options
Array.from(code.matchAll(reConfigBoolean)).forEach((match) => {
const index = match.index!
const name = match[1]
flagNotations.push(
parseFlag(name, true, index, index + match[0].length + 1, customTags, tsOptionDeclarations),
)
})
Array.from(code.matchAll(reConfigValue)).forEach((match) => {
const name = match[1]
if (name === 'filename')
return
const index = match.index!
const value = match[2]
flagNotations.push(
parseFlag(name, value, index, index + match[0].length + 1, customTags, tsOptionDeclarations),
)
})
return flagNotations
}
7 changes: 7 additions & 0 deletions packages/twoslash/src/public.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
export * from './error'
export * from './types'
export * from './defaults'

export {
createPositionConverter,

findCutNotations,
findFlagNotations,
findQueryMarkers,

removeCodeRanges,
resolveNodePositions,
} from './utils'

export {
validateCodeForErrors,
} from './validation'
8 changes: 8 additions & 0 deletions packages/twoslash/src/regexp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const reConfigBoolean = /^\/\/\s?@(\w+)$/mg
export const reConfigValue = /^\/\/\s?@(\w+):\s?(.+)$/mg
export const reAnnonateMarkers = /^\s*\/\/\s*\^(\?|\||\^+)( .*)?$/mg

export const reCutBefore = /^\/\/\s?---cut(-before)?---$/mg
export const reCutAfter = /^\/\/\s?---cut-after---$/mg
export const reCutStart = /^\/\/\s?---cut-start---$/mg
export const reCutEnd = /^\/\/\s?---cut-end---$/mg
95 changes: 94 additions & 1 deletion packages/twoslash/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { SourceFile } from 'typescript'
import { TwoslashError } from './error'
import type { NodeStartLength, NodeWithoutPosition, ParsedFlagNotation, Position, Range, TwoSlashNode, VirtualFile } from './types'
import type { NodeStartLength, NodeWithoutPosition, ParsedFlagNotation, Position, Range, TwoSlashNode, TwoSlashReturnMeta, VirtualFile } from './types'
import { defaultHandbookOptions } from './defaults'
import type { CompilerOptionDeclaration } from './types/internal'
import { reAnnonateMarkers, reConfigBoolean, reConfigValue, reCutAfter, reCutBefore, reCutEnd, reCutStart } from './regexp'

export function parsePrimitive(value: string, type: string): any {
// eslint-disable-next-line valid-typeof
Expand Down Expand Up @@ -345,3 +346,95 @@ export function parseFlag(
end,
}
}

export function findFlagNotations(code: string, customTags: string[], tsOptionDeclarations: CompilerOptionDeclaration[]) {
const flagNotations: ParsedFlagNotation[] = []

// #extract compiler options
Array.from(code.matchAll(reConfigBoolean)).forEach((match) => {
const index = match.index!
const name = match[1]
flagNotations.push(
parseFlag(name, true, index, index + match[0].length + 1, customTags, tsOptionDeclarations),
)
})
Array.from(code.matchAll(reConfigValue)).forEach((match) => {
const name = match[1]
if (name === 'filename')
return
const index = match.index!
const value = match[2]
flagNotations.push(
parseFlag(name, value, index, index + match[0].length + 1, customTags, tsOptionDeclarations),
)
})
return flagNotations
}

export function findCutNotations(code: string) {
const removals: Range[] = []

const cutBefore = [...code.matchAll(reCutBefore)]
const cutAfter = [...code.matchAll(reCutAfter)]
const cutStart = [...code.matchAll(reCutStart)]
const cutEnd = [...code.matchAll(reCutEnd)]

if (cutBefore.length) {
const last = cutBefore[cutBefore.length - 1]
removals.push([0, last.index! + last[0].length + 1])
}
if (cutAfter.length) {
const first = cutAfter[0]
removals.push([first.index!, code.length])
}
if (cutStart.length !== cutEnd.length) {
throw new TwoslashError(
`Mismatched cut markers`,
`You have ${cutStart.length} cut-starts and ${cutEnd.length} cut-ends`,
`Make sure you have a matching pair for each.`,
)
}
for (let i = 0; i < cutStart.length; i++) {
const start = cutStart[i]
const end = cutEnd[i]
if (start.index! > end.index!) {
throw new TwoslashError(
`Mismatched cut markers`,
`You have a cut-start at ${start.index} which is after the cut-end at ${end.index}`,
`Make sure you have a matching pair for each.`,
)
}
removals.push([start.index!, end.index! + end[0].length + 1])
}
return removals
}

export function findQueryMarkers(
code: string,
meta: Pick<TwoSlashReturnMeta, 'positionQueries' | 'positionCompletions' | 'positionHighlights' | 'removals'>,
getIndexOfLineAbove: (index: number) => number,
) {
if (code.includes('//')) {
Array.from(code.matchAll(reAnnonateMarkers)).forEach((match) => {
const type = match[1] as '?' | '|' | '^^'
const index = match.index!
meta.removals.push([index, index + match[0].length + 1])
const markerIndex = match[0].indexOf('^')
const targetIndex = getIndexOfLineAbove(index + markerIndex)
if (type === '?') {
meta.positionQueries.push(targetIndex)
}
else if (type === '|') {
meta.positionCompletions.push(targetIndex)
}
else {
const markerLength = match[0].lastIndexOf('^') - markerIndex + 1
meta.positionHighlights.push([
targetIndex,
targetIndex + markerLength,
])
}
})
}
return meta
}

0 comments on commit e59af90

Please sign in to comment.