Skip to content

Commit

Permalink
feat(loc): add loc helpers ; improve proxyBlock ; fix block issue
Browse files Browse the repository at this point in the history
  • Loading branch information
Tahul committed Jul 29, 2023
1 parent 33a7218 commit baa4d3a
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './proxy'
export * from './magic-sfc'
export * from './vue/create'
export * from './vue/sfc'
export * from './loc'
64 changes: 64 additions & 0 deletions src/loc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export function countLines(source: string) { return (source.match(/\n/g) || []).length }
export function lastLineLength(source: string) { return source.substring(source.lastIndexOf('\n')).length }

export function createSourceLocation(source: string) {
const endLine = countLines(source)
const endColumn = lastLineLength(source)

return {
start: {
offset: 0,
line: 0,
column: 0,
},
end: {
offset: source.length,
line: endLine,
column: endColumn,
},
source,
}
}

export function findAllSourceLocations(source: string, search: string) {
const locations = []
let startOffset = 0

while (startOffset < source.length) {
const index = source.indexOf(search, startOffset)
if (index === -1) {
break
}

const endOffset = index + search.length

// calculate line and column for start position
const preStartStr = source.substring(0, index)
const startLine = (preStartStr.match(/\n/g) || []).length
const startColumn = preStartStr.substring(preStartStr.lastIndexOf('\n')).length

// calculate line and column for end position
const preEndStr = source.substring(0, endOffset)
const endLine = (preEndStr.match(/\n/g) || []).length
const endColumn = preEndStr.substring(preEndStr.lastIndexOf('\n')).length

locations.push({
start: {
offset: index,
line: startLine,
column: startColumn,
},
end: {
offset: endOffset,
line: endLine,
column: endColumn,
},
source: search,
})

// move startOffset to after the found occurrence
startOffset = endOffset
}

return locations.length > 0 ? locations : null
}
25 changes: 18 additions & 7 deletions src/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import type MagicString from 'magic-string'
import type { SourceLocation } from './magic-sfc'
import { createSourceLocation } from './loc'

export interface MagicBlockBase {
loc: SourceLocation
loc?: SourceLocation
[key: string]: any
}

export type MagicBlock<T extends MagicBlockBase = MagicBlockBase> = T & MagicString

export function proxyBlock<T extends MagicBlockBase = MagicBlockBase>(
source: MagicString,
block: T,
block?: T,
handler: ProxyHandler<object> = {},
): MagicBlock<T> {
const { start: blockStart, end: blockEnd } = block?.loc || {}
const { start: blockStart, end: blockEnd } = block?.loc || createSourceLocation(source.toString())

// Recreate a local Magic String from the block content.
const snip: MagicString = source.snip(blockStart.offset, blockEnd.offset)
Expand Down Expand Up @@ -54,23 +55,33 @@ export function proxyBlock<T extends MagicBlockBase = MagicBlockBase>(
}

return new Proxy(
block,
block || {},
{
...handler,
get(target: T, key: string | symbol, receiver: any) {
if (key === 'source') { return source }
if (key === 'snip') { return snip }
if (key === 'source') {
return source
}
if (key in snip) {
if (Object.hasOwn(proxified, key)) { return (proxified as any)[key] }
return snip[key as unknown as keyof MagicString]
}
if (handler.get) { return handler.get(target, key, receiver) }
if (block && key in block) {
return block[key as any]
}
if (handler.get) {
return handler.get(target, key, receiver)
}
},
set(target: T, key: string | symbol, value: any, receiver: any) {
if (key in proxified) {
(proxified as any)[key] = value
return true
}
if (block && key in block) {
(block as any)[key] = value
return true
}
if (handler.set) { return handler.set(target, key, value, receiver) }
return false
},
Expand Down
20 changes: 19 additions & 1 deletion test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import MagicString, { SourceMap } from 'magic-string'
import { describe, expect, it } from 'vitest'
import { MagicSFC } from '../src'
import { MagicSFC, createSourceLocation, proxyBlock } from '../src'

describe('Magic SFC', () => {
it('Can create the class', () => {
Expand All @@ -22,4 +22,22 @@ describe('Magic SFC', () => {

expect(sfc.getSourcemap()).toBeInstanceOf(SourceMap)
})

it('Can access custom properties from proxified block', () => {
const source = '<script setup>let test: string</script>'

const block = proxyBlock(
new MagicString(source),
{
loc: createSourceLocation(source),
type: 'style',
lang: 'postcss',
attrs: {
lang: 'postcss',
},
},
)

expect(block.type).toBe('style')
})
})
43 changes: 43 additions & 0 deletions test/loc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, expect, it } from 'vitest'
import { countLines, createSourceLocation, findAllSourceLocations, lastLineLength } from '../src'
import { completeComponent, script } from './utils'

describe('LOC Helpers', () => {
it('Can create SourceLocation', () => {
const source = '<script setup>let test: string</script>'

const loc = createSourceLocation(source)

expect(loc).toStrictEqual({
start: {
offset: 0,
line: 0,
column: 0,
},
end: {
offset: source.length,
column: source.length,
line: 0,
},
source,
})
})

it('Can find source locations', () => {
const locations = findAllSourceLocations(completeComponent, script)

expect(locations[0]).toStrictEqual({
source: script,
start: {
offset: 3,
line: 1,
column: 3,
},
end: {
offset: 3 + script.length,
line: 1 + countLines(script),
column: 3 + lastLineLength(script),
},
})
})
})
12 changes: 12 additions & 0 deletions test/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const script = '<script>let baseScript: string</script>'
export const scriptSetup = '<script setup>let scriptSetup: string</script>'
export const template = '<template><div>Hello World!</div></template>'
export const style = '<style>div { color: blue; }</style>'
export const styleScoped = '<style scoped>.scoped { color: blue; }</style>'
export const completeComponent = `
${script}
${scriptSetup}
${template}
${style}
${styleScoped}
`
14 changes: 1 addition & 13 deletions test/vue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,7 @@ import MagicString, { SourceMap } from 'magic-string'
import { beforeEach, describe, expect, it } from 'vitest'
import { parse } from 'vue/compiler-sfc'
import { MagicVueSFC, magicVueSfcDefaultOptions } from '../src/vue/sfc'

const script = '<script>let baseScript: string</script>'
const scriptSetup = '<script setup>let scriptSetup: string</script>'
const template = '<template><div>Hello World!</div></template>'
const style = '<style>div { color: blue; }</style>'
const styleScoped = '<style scoped>.scoped { color: blue; }</style>'
const completeComponent = `
${script}
${scriptSetup}
${template}
${style}
${styleScoped}
`
import { completeComponent, script, scriptSetup, style, styleScoped, template } from './utils'

describe('Magic Vue SFC', () => {
beforeEach(() => {
Expand Down

0 comments on commit baa4d3a

Please sign in to comment.