mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
Detect and migrate static plugin usages (#14648)
This PR builds on top of the new [JS config to CSS migration](https://github.com/tailwindlabs/tailwindcss/pull/14651) and extends it to support migrating _static_ plugins. What are _static_ plugins you might ask? Static plugins are plugins where we can statically determine that these are coming from a different file (so there is nothing inside the JS config that creates them). An example for this is this config file: ```js import typographyPlugin from '@tailwindcss/typography' import { type Config } from 'tailwindcss' export default { content: ['./src/**/*.{js,jsx,ts,tsx}'], darkMode: 'selector', plugins: [typographyPlugin], } satisfies Config ``` Here, the `plugins` array only has one element and it is a static import from the `@tailwindcss/typography` module. In this PR we attempt to parse the config file via Tree-sitter to extract the following information from this file: - What are the contents of the `plugins` array - What are statically imported resources from the file We then check if _all_ entries in the `plugins` array are either static resources or _strings_ (something I saw working in some tests but I’m not sure it still does). We migrate the JS config file to CSS if all plugins are static and we can migrate them to CSS `@plugin` calls. ## Todo This will need to be rebased after the updated tests in #14648
This commit is contained in:
parent
4b19de3a45
commit
468cb5e99e
@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- _Upgrade (experimental)_: Migrate v3 PostCSS setups to v4 in some cases ([#14612](https://github.com/tailwindlabs/tailwindcss/pull/14612))
|
||||
- _Upgrade (experimental)_: Automatically discover JavaScript config files ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597))
|
||||
- _Upgrade (experimental)_: Migrate legacy classes to the v4 alternative ([#14643](https://github.com/tailwindlabs/tailwindcss/pull/14643))
|
||||
- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650))
|
||||
- _Upgrade (experimental)_: Migrate static JS configurations to CSS ([#14639](https://github.com/tailwindlabs/tailwindcss/pull/14639), [#14650](https://github.com/tailwindlabs/tailwindcss/pull/14650), [#14648](https://github.com/tailwindlabs/tailwindcss/pull/14648))
|
||||
- _Upgrade (experimental)_: Migrate `@media screen(…)` when running codemods ([#14603](https://github.com/tailwindlabs/tailwindcss/pull/14603))
|
||||
- _Upgrade (experimental)_: Inject `@config "…"` when a `tailwind.config.{js,ts,…}` is detected ([#14635](https://github.com/tailwindlabs/tailwindcss/pull/14635))
|
||||
- _Upgrade (experimental)_: Migrate `aria-*`, `data-*`, and `supports-*` variants from arbitrary values to bare values ([#14644](https://github.com/tailwindlabs/tailwindcss/pull/14644))
|
||||
|
||||
@ -102,6 +102,61 @@ test(
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'upgrades JS config files with plugins',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import customPlugin from './custom-plugin'
|
||||
|
||||
export default {
|
||||
plugins: [typography, customPlugin],
|
||||
} satisfies Config
|
||||
`,
|
||||
'custom-plugin.js': ts`
|
||||
export default function ({ addVariant }) {
|
||||
addVariant('inverted', '@media (inverted-colors: inverted)')
|
||||
addVariant('hocus', ['&:focus', '&:hover'])
|
||||
}
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin '@tailwindcss/typography';
|
||||
@plugin '../custom-plugin';
|
||||
"
|
||||
`)
|
||||
|
||||
expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
|
||||
"
|
||||
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'does not upgrade JS config files with functions in the theme config',
|
||||
{
|
||||
@ -231,66 +286,6 @@ test(
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
'does not upgrade JS config files with plugins',
|
||||
{
|
||||
fs: {
|
||||
'package.json': json`
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@tailwindcss/upgrade": "workspace:^"
|
||||
}
|
||||
}
|
||||
`,
|
||||
'tailwind.config.ts': ts`
|
||||
import { type Config } from 'tailwindcss'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import customPlugin from './custom-plugin'
|
||||
|
||||
export default {
|
||||
plugins: [typography, customPlugin],
|
||||
} satisfies Config
|
||||
`,
|
||||
'custom-plugin.js': ts`
|
||||
export default function ({ addVariant }) {
|
||||
addVariant('inverted', '@media (inverted-colors: inverted)')
|
||||
addVariant('hocus', ['&:focus', '&:hover'])
|
||||
}
|
||||
`,
|
||||
'src/input.css': css`
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`,
|
||||
},
|
||||
},
|
||||
async ({ exec, fs }) => {
|
||||
await exec('npx @tailwindcss/upgrade')
|
||||
|
||||
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- src/input.css ---
|
||||
@import 'tailwindcss';
|
||||
@config '../tailwind.config.ts';
|
||||
"
|
||||
`)
|
||||
|
||||
expect(await fs.dumpFiles('tailwind.config.ts')).toMatchInlineSnapshot(`
|
||||
"
|
||||
--- tailwind.config.ts ---
|
||||
import { type Config } from 'tailwindcss'
|
||||
import typography from '@tailwindcss/typography'
|
||||
import customPlugin from './custom-plugin'
|
||||
|
||||
export default {
|
||||
plugins: [typography, customPlugin],
|
||||
} satisfies Config
|
||||
"
|
||||
`)
|
||||
},
|
||||
)
|
||||
|
||||
test(
|
||||
`does not upgrade JS config files with inline plugins`,
|
||||
{
|
||||
|
||||
@ -39,7 +39,9 @@
|
||||
"postcss-selector-parser": "^6.1.2",
|
||||
"prettier": "^3.3.3",
|
||||
"string-byte-slice": "^3.0.0",
|
||||
"tailwindcss": "workspace:^"
|
||||
"tailwindcss": "workspace:^",
|
||||
"tree-sitter": "^0.21.1",
|
||||
"tree-sitter-typescript": "^0.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
|
||||
@ -55,11 +55,21 @@ export function migrateConfig(
|
||||
let absolute = path.resolve(source.base, source.pattern)
|
||||
css += `@source '${relativeToStylesheet(sheet, absolute)}';\n`
|
||||
}
|
||||
|
||||
if (jsConfigMigration.sources.length > 0) {
|
||||
css = css + '\n'
|
||||
}
|
||||
|
||||
for (let plugin of jsConfigMigration.plugins) {
|
||||
let relative =
|
||||
plugin.path[0] === '.'
|
||||
? relativeToStylesheet(sheet, path.resolve(plugin.base, plugin.path))
|
||||
: plugin.path
|
||||
css += `@plugin '${relative}';\n`
|
||||
}
|
||||
if (jsConfigMigration.plugins.length > 0) {
|
||||
css = css + '\n'
|
||||
}
|
||||
|
||||
cssConfig.append(postcss.parse(css + jsConfigMigration.css))
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge'
|
||||
import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config'
|
||||
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
|
||||
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
|
||||
import { findStaticPlugins } from './utils/extract-static-plugins'
|
||||
import { info } from './utils/renderer'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
@ -21,6 +22,7 @@ export type JSConfigMigration =
|
||||
// Could not convert the config file, need to inject it as-is in a @config directive
|
||||
null | {
|
||||
sources: { base: string; pattern: string }[]
|
||||
plugins: { base: string; path: string }[]
|
||||
css: string
|
||||
}
|
||||
|
||||
@ -41,6 +43,7 @@ export async function migrateJsConfig(
|
||||
}
|
||||
|
||||
let sources: { base: string; pattern: string }[] = []
|
||||
let plugins: { base: string; path: string }[] = []
|
||||
let cssConfigs: string[] = []
|
||||
|
||||
if ('darkMode' in unresolvedConfig) {
|
||||
@ -56,8 +59,16 @@ export async function migrateJsConfig(
|
||||
if (themeConfig) cssConfigs.push(themeConfig)
|
||||
}
|
||||
|
||||
let simplePlugins = findStaticPlugins(source)
|
||||
if (simplePlugins !== null) {
|
||||
for (let plugin of simplePlugins) {
|
||||
plugins.push({ base, path: plugin })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sources,
|
||||
plugins,
|
||||
css: cssConfigs.join('\n'),
|
||||
}
|
||||
}
|
||||
@ -168,7 +179,9 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
|
||||
return ['string', 'number', 'boolean', 'undefined'].includes(typeof value)
|
||||
}
|
||||
|
||||
if (!isSimpleValue(unresolvedConfig)) {
|
||||
// Plugins are more complex, so we have a special heuristics for them.
|
||||
let { plugins, ...remainder } = unresolvedConfig
|
||||
if (!isSimpleValue(remainder)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -186,7 +199,7 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
if (unresolvedConfig.plugins && unresolvedConfig.plugins.length > 0) {
|
||||
if (findStaticPlugins(source) === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
import dedent from 'dedent'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { extractStaticImportMap, findStaticPlugins } from './extract-static-plugins'
|
||||
|
||||
const js = dedent
|
||||
|
||||
describe('findStaticPlugins', () => {
|
||||
test('parses all export styles', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import plugin1 from './plugin1'
|
||||
import * as plugin2 from './plugin2'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')]
|
||||
}
|
||||
`),
|
||||
).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4'])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import plugin1 from './plugin1'
|
||||
import * as plugin2 from './plugin2'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')]
|
||||
} as any
|
||||
`),
|
||||
).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4'])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import plugin1 from './plugin1'
|
||||
import * as plugin2 from './plugin2'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1, plugin2, 'plugin3', require('./plugin4')]
|
||||
} satisfies any
|
||||
`),
|
||||
).toEqual(['./plugin1', './plugin2', 'plugin3', './plugin4'])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
const plugin1 = require('./plugin1')
|
||||
|
||||
module.exports = {
|
||||
plugins: [plugin1, 'plugin2', require('./plugin3')]
|
||||
} as any
|
||||
`),
|
||||
).toEqual(['./plugin1', 'plugin2', './plugin3'])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
const plugin1 = require('./plugin1')
|
||||
|
||||
module.exports = {
|
||||
plugins: [plugin1, 'plugin2', require('./plugin3')]
|
||||
} satisfies any
|
||||
`),
|
||||
).toEqual(['./plugin1', 'plugin2', './plugin3'])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
const plugin1 = require('./plugin1')
|
||||
|
||||
module.exports = {
|
||||
plugins: [plugin1, 'plugin2', require('./plugin3')]
|
||||
}
|
||||
`),
|
||||
).toEqual(['./plugin1', 'plugin2', './plugin3'])
|
||||
})
|
||||
|
||||
test('bails out on inline plugins', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import plugin1 from './plugin1'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1, () => {} ]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
let plugin1 = () => {}
|
||||
|
||||
export default {
|
||||
plugins: [plugin1]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
test('bails out on non `require` calls', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
export default {
|
||||
plugins: [frequire('./plugin1')]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
test('bails out on named imports for plugins', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import {plugin1} from './plugin1'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
test('bails for plugins with options', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
import plugin1 from './plugin1'
|
||||
|
||||
export default {
|
||||
plugins: [plugin1({foo:'bar'})]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
export default {
|
||||
plugins: [require('@tailwindcss/typography')({foo:'bar'})]
|
||||
}
|
||||
`),
|
||||
).toEqual(null)
|
||||
})
|
||||
|
||||
test('returns no plugins if none are exported', () => {
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
export default {
|
||||
plugins: []
|
||||
}
|
||||
`),
|
||||
).toEqual([])
|
||||
|
||||
expect(
|
||||
findStaticPlugins(js`
|
||||
export default {}
|
||||
`),
|
||||
).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractStaticImportMap', () => {
|
||||
test('extracts different kind of imports from an ESM file', () => {
|
||||
let extracted = extractStaticImportMap(js`
|
||||
import plugin1 from './plugin1'
|
||||
import * as plugin2 from './plugin2'
|
||||
import plugin6, { plugin3, plugin4, default as plugin5 } from './plugin3'
|
||||
import plugin8, * as plugin7 from './plugin7'
|
||||
`)
|
||||
|
||||
expect(extracted).toEqual({
|
||||
plugin1: { module: './plugin1', export: null },
|
||||
plugin2: { module: './plugin2', export: '*' },
|
||||
plugin3: { module: './plugin3', export: 'plugin3' },
|
||||
plugin4: { module: './plugin3', export: 'plugin4' },
|
||||
plugin5: { module: './plugin3', export: 'default' },
|
||||
plugin6: { module: './plugin3', export: null },
|
||||
plugin7: { module: './plugin7', export: '*' },
|
||||
plugin8: { module: './plugin7', export: null },
|
||||
})
|
||||
})
|
||||
|
||||
test('extracts different kind of imports from an CJS file', () => {
|
||||
let extracted = extractStaticImportMap(js`
|
||||
const plugin1 = require('./plugin1')
|
||||
let plugin2 = require('./plugin2')
|
||||
var plugin3 = require('./plugin3')
|
||||
|
||||
const {plugin4, foo: plugin5, ...plugin6} = require('./plugin4')
|
||||
let {plugin7, foo: plugin8, ...plugin9} = require('./plugin5')
|
||||
var {plugin10, foo: plugin11, ...plugin12} = require('./plugin6')
|
||||
`)
|
||||
|
||||
expect(extracted).toEqual({
|
||||
plugin1: { module: './plugin1', export: null },
|
||||
plugin2: { module: './plugin2', export: null },
|
||||
plugin3: { module: './plugin3', export: null },
|
||||
plugin4: { module: './plugin4', export: 'plugin4' },
|
||||
plugin5: { module: './plugin4', export: 'foo' },
|
||||
plugin6: { module: './plugin4', export: '*' },
|
||||
plugin7: { module: './plugin5', export: 'plugin7' },
|
||||
plugin8: { module: './plugin5', export: 'foo' },
|
||||
plugin9: { module: './plugin5', export: '*' },
|
||||
plugin10: { module: './plugin6', export: 'plugin10' },
|
||||
plugin11: { module: './plugin6', export: 'foo' },
|
||||
plugin12: { module: './plugin6', export: '*' },
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,199 @@
|
||||
import Parser from 'tree-sitter'
|
||||
import TS from 'tree-sitter-typescript'
|
||||
|
||||
let parser = new Parser()
|
||||
parser.setLanguage(TS.typescript)
|
||||
const treesitter = String.raw
|
||||
|
||||
const PLUGINS_QUERY = new Parser.Query(
|
||||
TS.typescript,
|
||||
treesitter`
|
||||
; export default {}
|
||||
(export_statement
|
||||
value: (satisfies_expression (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
))?
|
||||
value: (as_expression (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
))?
|
||||
value: (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
)?
|
||||
)
|
||||
|
||||
; module.exports = {}
|
||||
(expression_statement
|
||||
(assignment_expression
|
||||
left: (member_expression) @left (#eq? @left "module.exports")
|
||||
right: (satisfies_expression (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
))?
|
||||
right: (as_expression (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
))?
|
||||
right: (object
|
||||
(pair
|
||||
key: (property_identifier) @_name (#eq? @_name "plugins")
|
||||
value: (array) @imports
|
||||
)
|
||||
)?
|
||||
)
|
||||
)
|
||||
`,
|
||||
)
|
||||
export function findStaticPlugins(source: string): string[] | null {
|
||||
try {
|
||||
let tree = parser.parse(source)
|
||||
let root = tree.rootNode
|
||||
|
||||
let imports = extractStaticImportMap(source)
|
||||
let captures = PLUGINS_QUERY.matches(root)
|
||||
|
||||
let plugins = []
|
||||
for (let match of captures) {
|
||||
for (let capture of match.captures) {
|
||||
if (capture.name !== 'imports') continue
|
||||
|
||||
for (let pluginDefinition of capture.node.children) {
|
||||
if (
|
||||
pluginDefinition.type === '[' ||
|
||||
pluginDefinition.type === ']' ||
|
||||
pluginDefinition.type === ','
|
||||
)
|
||||
continue
|
||||
|
||||
switch (pluginDefinition.type) {
|
||||
case 'identifier':
|
||||
let source = imports[pluginDefinition.text]
|
||||
if (!source || (source.export !== null && source.export !== '*')) {
|
||||
return null
|
||||
}
|
||||
plugins.push(source.module)
|
||||
break
|
||||
case 'string':
|
||||
plugins.push(pluginDefinition.children[1].text)
|
||||
break
|
||||
case 'call_expression':
|
||||
// allow require('..') calls
|
||||
if (pluginDefinition.children?.[0]?.text !== 'require') return null
|
||||
let firstArgument = pluginDefinition.children?.[1]?.children?.[1]?.children?.[1]?.text
|
||||
if (typeof firstArgument !== 'string') return null
|
||||
plugins.push(firstArgument)
|
||||
break
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return plugins
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const IMPORT_QUERY = new Parser.Query(
|
||||
TS.typescript,
|
||||
treesitter`
|
||||
; ESM import
|
||||
(import_statement
|
||||
(import_clause
|
||||
(identifier)? @default
|
||||
(named_imports
|
||||
(import_specifier
|
||||
name: (identifier) @imported-name
|
||||
alias: (identifier)? @imported-alias
|
||||
)
|
||||
)?
|
||||
(namespace_import (identifier) @imported-namespace)?
|
||||
)
|
||||
(string
|
||||
(string_fragment) @imported-from)
|
||||
)
|
||||
|
||||
; CJS require
|
||||
(variable_declarator
|
||||
name: (identifier)? @default
|
||||
name: (object_pattern
|
||||
(shorthand_property_identifier_pattern)? @imported-name
|
||||
(pair_pattern
|
||||
key: (property_identifier) @imported-name
|
||||
value: (identifier) @imported-alias
|
||||
)?
|
||||
(rest_pattern
|
||||
(identifier) @imported-namespace
|
||||
)?
|
||||
)?
|
||||
value: (call_expression
|
||||
function: (identifier) @_fn (#eq? @_fn "require")
|
||||
arguments: (arguments
|
||||
(string
|
||||
(string_fragment) @imported-from
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
`,
|
||||
)
|
||||
|
||||
export function extractStaticImportMap(source: string) {
|
||||
let tree = parser.parse(source)
|
||||
let root = tree.rootNode
|
||||
|
||||
let captures = IMPORT_QUERY.matches(root)
|
||||
|
||||
let imports: Record<string, { module: string; export: string | null }> = {}
|
||||
for (let match of captures) {
|
||||
let toImport: { name: string; export: null | string }[] = []
|
||||
let from = ''
|
||||
for (let i = 0; i < match.captures.length; i++) {
|
||||
let capture = match.captures[i]
|
||||
|
||||
switch (capture.name) {
|
||||
case 'default':
|
||||
toImport.push({ name: capture.node.text, export: null })
|
||||
break
|
||||
case 'imported-name':
|
||||
toImport.push({ name: capture.node.text, export: capture.node.text })
|
||||
break
|
||||
case 'imported-from':
|
||||
from = capture.node.text
|
||||
break
|
||||
case 'imported-namespace':
|
||||
toImport.push({ name: capture.node.text, export: '*' })
|
||||
break
|
||||
case 'imported-alias':
|
||||
if (toImport.length < 1) {
|
||||
throw new Error('Unexpected alias: ' + JSON.stringify(captures, null, 2))
|
||||
}
|
||||
let prevImport = toImport[toImport.length - 1]
|
||||
let name = prevImport.name
|
||||
prevImport.export = name
|
||||
prevImport.name = capture.node.text
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for (let { name, export: exportSource } of toImport) {
|
||||
imports[name] = { module: from, export: exportSource }
|
||||
}
|
||||
}
|
||||
|
||||
return imports
|
||||
}
|
||||
@ -1,3 +1,6 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports":true
|
||||
}
|
||||
}
|
||||
|
||||
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
@ -312,6 +312,12 @@ importers:
|
||||
tailwindcss:
|
||||
specifier: workspace:^
|
||||
version: link:../tailwindcss
|
||||
tree-sitter:
|
||||
specifier: ^0.21.1
|
||||
version: 0.21.1
|
||||
tree-sitter-typescript:
|
||||
specifier: ^0.23.0
|
||||
version: 0.23.0(tree-sitter@0.21.1)
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: 'catalog:'
|
||||
@ -2384,6 +2390,14 @@ packages:
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-addon-api@8.1.0:
|
||||
resolution: {integrity: sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-gyp-build@4.8.2:
|
||||
resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==}
|
||||
hasBin: true
|
||||
|
||||
node-releases@2.0.18:
|
||||
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
|
||||
|
||||
@ -2917,6 +2931,18 @@ packages:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
tree-sitter-typescript@0.23.0:
|
||||
resolution: {integrity: sha512-hRy5O9d+9ON4HxIWWxkI4zonrw2v/WNN1JoiGW5HkXfC9K2R3p53ugMvs6Vs4T7ASCwggsoQ75LNdgpExC/zgQ==}
|
||||
peerDependencies:
|
||||
tree-sitter: ^0.21.0
|
||||
tree_sitter: '*'
|
||||
peerDependenciesMeta:
|
||||
tree_sitter:
|
||||
optional: true
|
||||
|
||||
tree-sitter@0.21.1:
|
||||
resolution: {integrity: sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==}
|
||||
|
||||
ts-api-utils@1.3.0:
|
||||
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
|
||||
engines: {node: '>=16'}
|
||||
@ -5145,6 +5171,10 @@ snapshots:
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-addon-api@8.1.0: {}
|
||||
|
||||
node-gyp-build@4.8.2: {}
|
||||
|
||||
node-releases@2.0.18: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
@ -5677,6 +5707,17 @@ snapshots:
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
tree-sitter-typescript@0.23.0(tree-sitter@0.21.1):
|
||||
dependencies:
|
||||
node-addon-api: 8.1.0
|
||||
node-gyp-build: 4.8.2
|
||||
tree-sitter: 0.21.1
|
||||
|
||||
tree-sitter@0.21.1:
|
||||
dependencies:
|
||||
node-addon-api: 8.1.0
|
||||
node-gyp-build: 4.8.2
|
||||
|
||||
ts-api-utils@1.3.0(typescript@5.5.4):
|
||||
dependencies:
|
||||
typescript: 5.5.4
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user