Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sxzz/ast-explorer
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: dc8fc306fce43f63c897a8f84c52ce27a4d68c3f
Choose a base ref
..
head repository: sxzz/ast-explorer
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 6abe968b5056124550adab6fc4e37caa88cd78a6
Choose a head ref
Showing with 266 additions and 99 deletions.
  1. +8 −0 README.md
  2. +2 −1 composables/options.ts
  3. +103 −68 composables/parser/javascript/BabelGui.vue
  4. +1 −1 composables/parser/javascript/OxcGui.vue
  5. +12 −10 composables/parser/javascript/oxc.ts
  6. +11 −0 nuxt.config.ts
  7. +4 −4 package.json
  8. +124 −14 pnpm-lock.yaml
  9. +1 −1 state/parser/options.ts
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -115,6 +115,14 @@ To contribute to the project, see [Contribution Guide](https://github.com/sxzz/c
- https://astexplorer.net/
- https://play.swc.rs/

## Sponsors

<p align="center">
<a href="https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg">
<img src='https://cdn.jsdelivr.net/gh/sxzz/sponsors/sponsors.svg'/>
</a>
</p>

## License

[AGPL-3.0](./LICENSE) License © 2023-PRESENT [三咲智子](https://github.com/sxzz)
3 changes: 2 additions & 1 deletion composables/options.ts
Original file line number Diff line number Diff line change
@@ -36,7 +36,8 @@ export function makeUseOption<O extends object>() {
: never
> =>
useOptions(
(opt: O) => getWithPath(opt, keys as any) ?? defaultValue,
(opt?: O) =>
(opt == null ? null : getWithPath(opt, keys as any)) ?? defaultValue,
(value, opt) => {
let obj: any = opt
let key: string
171 changes: 103 additions & 68 deletions composables/parser/javascript/BabelGui.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,84 @@
<script lang="ts">
import { useOptions } from '~/state/parser/options'
import type { ParserOptions, ParserPlugin } from '@babel/parser'
import type {
ParserOptions,
ParserPlugin,
ParserPluginWithOptions,
} from '@babel/parser'
function isPluginOf(plugin: ParserPlugin, name: ParserPlugin & string) {
return plugin === name || (Array.isArray(plugin) && plugin[0] === name)
}
type ExtractPluginOptions<T extends ParserPlugin & string> = Extract<
ParserPluginWithOptions,
[T, any]
>[1]
function getPluginOptions<T extends ParserPlugin & string>(
plugins: ParserPlugin[] | undefined,
name: T,
): boolean | ExtractPluginOptions<T> {
const plugin = plugins?.find((p) => isPluginOf(p, name))
if (!plugin) return false
return Array.isArray(plugin) ? plugin[1] : true
}
const useOption = makeUseOption<ParserOptions>()
function usePlugin(name: ParserPlugin, deps: Ref<boolean | undefined>[] = []) {
function usePlugin<T extends ParserPlugin & string>(
name: T,
options: {
defaultOptions: ExtractPluginOptions<T>
deps?: Ref<boolean | undefined>[]
},
): WritableComputedRef<
ExtractPluginOptions<T>,
ExtractPluginOptions<T> | boolean
>
function usePlugin<T extends ParserPlugin & string>(
name: T,
options?: { deps?: Ref<boolean | undefined>[] },
): WritableComputedRef<boolean>
function usePlugin<T extends ParserPlugin & string>(
name: T,
{
defaultOptions,
deps = [],
}: {
defaultOptions?: ExtractPluginOptions<T>
deps?: Ref<boolean | undefined>[]
} = {},
): WritableComputedRef<boolean | ExtractPluginOptions<T>> {
const options = ref<any>({ ...defaultOptions })
if (defaultOptions) {
watch(options, () => (value.value = options.value), { deep: true })
}
const value = useOptions(
(opt: ParserOptions) => !!opt.plugins?.includes?.(name),
(opt?: ParserOptions) => {
const pluginOptions = getPluginOptions(opt?.plugins, name)
if (pluginOptions && defaultOptions) {
options.value =
pluginOptions === true ? { ...defaultOptions } : pluginOptions
return options.value
} else {
return pluginOptions
}
},
(value, opt) => {
if (!Array.isArray(opt.plugins)) opt.plugins = []
if (!Array.isArray(opt.plugins)) {
opt.plugins = []
} else {
opt.plugins = opt.plugins.filter((p) => !isPluginOf(p, name))
}
if (value) {
if (value !== false && value != null) {
deps.forEach((dep) => !dep.value && (dep.value = true))
opt.plugins.push(name)
} else {
opt.plugins = del(opt.plugins, [name])
if (value === true && defaultOptions) {
value = defaultOptions
}
opt.plugins.push(value === true ? name : ([name, value] as any))
}
},
)
@@ -67,13 +132,14 @@ const v8intrinsic = usePlugin('v8intrinsic')
// proposals
const doExpressions = usePlugin('doExpressions')
const asyncDoExpressions = usePlugin('asyncDoExpressions', [doExpressions])
const asyncDoExpressions = usePlugin('asyncDoExpressions', {
deps: [doExpressions],
})
const decorators = useOptions(
(opt: ParserOptions) =>
!!(
opt.plugins?.includes?.('decorators') ||
opt.plugins?.includes?.('decorators-legacy')
(opt?: ParserOptions) =>
opt?.plugins?.some(
(p) => isPluginOf(p, 'decorators') || isPluginOf(p, 'decorators-legacy'),
),
(value, opt) => {
if (!Array.isArray(opt.plugins)) opt.plugins = []
@@ -85,7 +151,8 @@ const decorators = useOptions(
},
)
const decoratorsLegacy = useOptions(
(opt: ParserOptions) => opt.plugins?.includes?.('decorators-legacy'),
(opt?: ParserOptions) =>
opt?.plugins?.some((p) => isPluginOf(p, 'decorators-legacy')),
(value, opt) => {
if (!Array.isArray(opt.plugins)) opt.plugins = []
if (value) {
@@ -98,54 +165,26 @@ const decoratorsLegacy = useOptions(
},
)
const pipelineOperator = useOptions(
(opt: ParserOptions) =>
opt.plugins?.some((n) => Array.isArray(n) && n[0] === 'pipelineOperator'),
(value, opt) => {
if (!Array.isArray(opt.plugins)) opt.plugins = []
if (value) {
opt.plugins.push([
'pipelineOperator',
{ proposal: 'hack', topicToken: '#' },
])
} else {
opt.plugins = opt.plugins.filter(
(n) => !Array.isArray(n) || n[0] !== 'pipelineOperator',
)
}
const optionalChainingAssign = usePlugin('optionalChainingAssign', {
defaultOptions: {
version: '2023-07',
},
)
function findPipelineOperator(optPlugins: ParserOptions['plugins']) {
return optPlugins?.find(
(n) => Array.isArray(n) && n[0] === 'pipelineOperator',
)
}
})
const pipelineOperatorProposal = useOptions(
(opt: ParserOptions) => findPipelineOperator(opt.plugins)?.[1].proposal,
(value, opt) => {
const pipelineOperator = findPipelineOperator(opt.plugins)
if (!pipelineOperator) return
if (value === 'fsharp') {
pipelineOperator[1].proposal = value
delete pipelineOperator[1].topicToken
} else if (value === 'hack') {
pipelineOperator[1].proposal = value
pipelineOperator[1].topicToken = '#'
}
const pipelineOperator = usePlugin('pipelineOperator', {
defaultOptions: {
proposal: 'hack',
topicToken: '%',
},
)
const pipelineOperatorTopicToken = useOptions(
(opt: ParserOptions) => findPipelineOperator(opt.plugins)?.[1].topicToken,
(value, opt) => {
const pipelineOperator = findPipelineOperator(opt.plugins)
if (!pipelineOperator) return
pipelineOperator[1].topicToken = value
},
)
const decoratorAutoAccessors = usePlugin('decoratorAutoAccessors', [decorators])
})
const pipelineOperatorEnable = computed({
get: () => !!pipelineOperator.value,
set: (value) => (pipelineOperator.value = value),
})
const decoratorAutoAccessors = usePlugin('decoratorAutoAccessors', {
deps: [decorators],
})
const decimal = usePlugin('decimal')
const deferredImportEvaluation = usePlugin('deferredImportEvaluation')
const destructuringPrivate = usePlugin('destructuringPrivate')
@@ -156,7 +195,6 @@ const functionSent = usePlugin('functionSent')
const deprecatedImportAssert = usePlugin('deprecatedImportAssert')
const importReflection = usePlugin('importReflection')
const moduleBlocks = usePlugin('moduleBlocks')
const optionalChainingAssign = usePlugin('optionalChainingAssign')
const partialApplication = usePlugin('partialApplication')
const recordAndTuple = usePlugin('recordAndTuple')
const sourcePhaseImports = usePlugin('sourcePhaseImports')
@@ -314,24 +352,21 @@ const throwExpressions = usePlugin('throwExpressions')

<label>
<!-- Stage 2 -->
<input v-model="pipelineOperator" type="checkbox" switch />
<input v-model="pipelineOperatorEnable" type="checkbox" switch />
<span>pipelineOperator</span>
</label>

<label ml6>
<label v-if="pipelineOperator" ml6>
<span>proposal</span>
<select v-model="pipelineOperatorProposal" :disabled="!pipelineOperator">
<select v-model="pipelineOperator.proposal">
<option value="hack">hack</option>
<option value="fsharp">fsharp</option>
</select>
</label>

<label ml6>
<label v-if="pipelineOperator && pipelineOperator.proposal === 'hack'" ml6>
<span>topicToken</span>
<select
v-model="pipelineOperatorTopicToken"
:disabled="!pipelineOperator || pipelineOperatorProposal !== 'hack'"
>
<select v-model="pipelineOperator.topicToken">
<option value="%">%</option>
<option value="#">#</option>
<option value="^">^</option>
2 changes: 1 addition & 1 deletion composables/parser/javascript/OxcGui.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import type { ParserOptions } from '@oxc-parser/wasm'
import type { ParserOptions } from './oxc'
const useOption = makeUseOption<ParserOptions>()
</script>

22 changes: 12 additions & 10 deletions composables/parser/javascript/oxc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Parser } from '..'
import type * as Oxc from '@oxc-parser/wasm/web/oxc_parser_wasm'
import type * as Oxc from 'oxc-parser'

export const oxc: Parser<typeof Oxc, Partial<Oxc.ParserOptions>> = {
export type ParserOptions = Oxc.ParserOptions & { sourceFilename: string }

export const oxc: Parser<typeof Oxc, Partial<ParserOptions>> = {
id: 'oxc',
label: 'Oxc',
icon: 'https://cdn.jsdelivr.net/gh/oxc-project/oxc-assets/round.svg',
@@ -14,15 +16,15 @@ export const oxc: Parser<typeof Oxc, Partial<Oxc.ParserOptions>> = {
},
editorLanguage: 'json',
},
pkgName: '@oxc-parser/wasm',
getModuleUrl: (pkg) => getJsdelivrUrl(pkg, `/web/oxc_parser_wasm.js`),
init: (url) =>
importUrl(url).then(async (mod: typeof Oxc) => {
await mod.default()
return mod
}),
pkgName: '@oxc-parser/binding-wasm32-wasi',
getModuleUrl: (pkg) => getJsdelivrUrl(pkg, '/browser-bundle.mjs'),
init: (url) => importUrl(url),
parse(code, options) {
const { program, comments, errors } = this.parseSync(code, { ...options })
const { program, comments, errors } = this.parseSync(
options.sourceFilename ?? 'test.js',
code,
{ sourceType: options.sourceType },
)
return { program, comments, errors }
},
editorLanguage(options) {
11 changes: 11 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -13,6 +13,17 @@ export default defineNuxtConfig({
},
},
},
nitro: {
routeRules: {
'/**': {
headers: {
// cross origin isolation is required since oxc-parser uses shared array buffer
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
},
},
devtools: {
enabled: true,
},
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -34,8 +34,7 @@
"@iconify-json/ri": "^1.2.5",
"@iconify-json/vscode-icons": "^1.2.16",
"@nuxt/kit": "^3.16.1",
"@oxc-parser/wasm": "^0.60.0",
"@swc/wasm-web": "1.11.9",
"@swc/wasm-web": "1.11.12",
"@sxzz/eslint-config": "^6.1.0",
"@sxzz/prettier-config": "^2.2.1",
"@types/css-tree": "^2.3.10",
@@ -64,6 +63,7 @@
"nuxt": "^3.16.1",
"nuxt-monaco-editor": "^1.3.1",
"nuxt-umami": "^3.2.0",
"oxc-parser": "^0.60.0",
"php-parser": "^3.2.2",
"postcss": "^8.5.3",
"prettier": "^3.5.3",
@@ -73,10 +73,10 @@
"serve": "^14.2.4",
"simple-git": "^3.27.0",
"sql-parser-cst": "^0.33.1",
"svelte": "^5.25.2",
"svelte": "^5.25.3",
"tsx": "^4.19.3",
"typescript": "~5.8.2",
"unenv": "^2.0.0-rc.14",
"unenv": "^2.0.0-rc.15",
"unocss": "^66.1.0-beta.6",
"unplugin-replace": "^0.5.1",
"vue-tsc": "^2.2.8",
Loading