fix: prevent asset conflicts between React and Grid.js versions

Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -0,0 +1,30 @@
import { addDefault, addNamed } from '@babel/helper-module-imports'
export function addImport(
state,
importSource /*: string */,
importedSpecifier /*: string */,
nameHint /* ?: string */
) {
let cacheKey = ['import', importSource, importedSpecifier].join(':')
if (state[cacheKey] === undefined) {
let importIdentifier
if (importedSpecifier === 'default') {
importIdentifier = addDefault(state.file.path, importSource, { nameHint })
} else {
importIdentifier = addNamed(
state.file.path,
importedSpecifier,
importSource,
{
nameHint
}
)
}
state[cacheKey] = importIdentifier.name
}
return {
type: 'Identifier',
name: state[cacheKey]
}
}

View File

@@ -0,0 +1,14 @@
export default function createNodeEnvConditional(t, production, development) {
return t.conditionalExpression(
t.binaryExpression(
'===',
t.memberExpression(
t.memberExpression(t.identifier('process'), t.identifier('env')),
t.identifier('NODE_ENV')
),
t.stringLiteral('production')
),
production,
development
)
}

View File

@@ -0,0 +1,102 @@
import { getLabelFromPath } from './label'
import { getTargetClassName } from './get-target-class-name'
import createNodeEnvConditional from './create-node-env-conditional'
const getKnownProperties = (t, node) =>
new Set(
node.properties
.filter(n => t.isObjectProperty(n) && !n.computed)
.map(n => (t.isIdentifier(n.key) ? n.key.name : n.key.value))
)
const createObjectSpreadLike = (t, file, ...objs) =>
t.callExpression(file.addHelper('extends'), [t.objectExpression([]), ...objs])
export let getStyledOptions = (t, path, state) => {
const autoLabel = state.opts.autoLabel || 'dev-only'
let args = path.node.arguments
let optionsArgument = args.length >= 2 ? args[1] : null
let prodProperties = []
let devProperties = null
let knownProperties =
optionsArgument && t.isObjectExpression(optionsArgument)
? getKnownProperties(t, optionsArgument)
: new Set()
if (!knownProperties.has('target')) {
prodProperties.push(
t.objectProperty(
t.identifier('target'),
t.stringLiteral(getTargetClassName(state, t))
)
)
}
let label =
autoLabel !== 'never' && !knownProperties.has('label')
? getLabelFromPath(path, state, t)
: null
if (label) {
const labelNode = t.objectProperty(
t.identifier('label'),
t.stringLiteral(label)
)
switch (autoLabel) {
case 'always':
prodProperties.push(labelNode)
break
case 'dev-only':
devProperties = [labelNode]
break
}
}
if (optionsArgument) {
// for some reason `.withComponent` transformer gets requeued
// so check if this has been already transpiled to avoid double wrapping
if (
t.isConditionalExpression(optionsArgument) &&
t.isBinaryExpression(optionsArgument.test) &&
t.buildMatchMemberExpression('process.env.NODE_ENV')(
optionsArgument.test.left
)
) {
return optionsArgument
}
if (!t.isObjectExpression(optionsArgument)) {
const prodNode = createObjectSpreadLike(
t,
state.file,
t.objectExpression(prodProperties),
optionsArgument
)
return devProperties
? createNodeEnvConditional(
t,
prodNode,
t.cloneNode(
createObjectSpreadLike(
t,
state.file,
t.objectExpression(prodProperties.concat(devProperties)),
optionsArgument
)
)
)
: prodNode
}
prodProperties.unshift(...optionsArgument.properties)
}
return devProperties
? createNodeEnvConditional(
t,
t.objectExpression(prodProperties),
t.cloneNode(t.objectExpression(prodProperties.concat(devProperties)))
)
: t.objectExpression(prodProperties)
}

View File

@@ -0,0 +1,51 @@
import findRoot from 'find-root'
import memoize from '@emotion/memoize'
import nodePath from 'path'
import hashString from '@emotion/hash'
import escapeRegexp from 'escape-string-regexp'
let hashArray = (arr /*: Array<string> */) => hashString(arr.join(''))
const unsafeRequire = require
const getPackageRootPath = memoize(filename => findRoot(filename))
const separator = new RegExp(escapeRegexp(nodePath.sep), 'g')
const normalizePath = path => nodePath.normalize(path).replace(separator, '/')
export function getTargetClassName(state, t) {
if (state.emotionTargetClassNameCount === undefined) {
state.emotionTargetClassNameCount = 0
}
const hasFilepath =
state.file.opts.filename && state.file.opts.filename !== 'unknown'
const filename = hasFilepath ? state.file.opts.filename : ''
// normalize the file path to ignore folder structure
// outside the current node project and arch-specific delimiters
let moduleName = ''
let rootPath = filename
try {
rootPath = getPackageRootPath(filename)
moduleName = unsafeRequire(rootPath + '/package.json').name
} catch (err) {}
const finalPath =
filename === rootPath ? 'root' : filename.slice(rootPath.length)
const positionInFile = state.emotionTargetClassNameCount++
const stuffToHash = [moduleName]
if (finalPath) {
stuffToHash.push(normalizePath(finalPath))
} else {
stuffToHash.push(state.file.code)
}
const stableClassName = `e${hashArray(stuffToHash)}${positionInFile}`
return stableClassName
}

12
node_modules/@emotion/babel-plugin/src/utils/index.js generated vendored Normal file
View File

@@ -0,0 +1,12 @@
export { getLabelFromPath } from './label'
export { getSourceMap } from './source-maps'
export { getTargetClassName } from './get-target-class-name'
export { simplifyObject } from './object-to-string'
export { transformExpressionWithStyles } from './transform-expression-with-styles'
export { getStyledOptions } from './get-styled-options'
export {
appendStringReturningExpressionToArguments,
joinStringLiterals
} from './strings'
export { addImport } from './add-import'
export { createTransformerMacro } from './transformer-macro'

192
node_modules/@emotion/babel-plugin/src/utils/label.js generated vendored Normal file
View File

@@ -0,0 +1,192 @@
import nodePath from 'path'
/*
type LabelFormatOptions = {
name: string,
path: string
}
*/
const invalidClassNameCharacters = /[!"#$%&'()*+,./:;<=>?@[\]^`|}~{]/g
const sanitizeLabelPart = (labelPart /*: string */) =>
labelPart.trim().replace(invalidClassNameCharacters, '-')
function getLabel(
identifierName /* ?: string */,
labelFormat /* ?: string | (LabelFormatOptions => string) */,
filename /*: string */
) {
if (!identifierName) return null
const sanitizedName = sanitizeLabelPart(identifierName)
if (!labelFormat) {
return sanitizedName
}
if (typeof labelFormat === 'function') {
return labelFormat({
name: sanitizedName,
path: filename
})
}
const parsedPath = nodePath.parse(filename)
let localDirname = nodePath.basename(parsedPath.dir)
let localFilename = parsedPath.name
if (localFilename === 'index') {
localFilename = localDirname
}
return labelFormat
.replace(/\[local\]/gi, sanitizedName)
.replace(/\[filename\]/gi, sanitizeLabelPart(localFilename))
.replace(/\[dirname\]/gi, sanitizeLabelPart(localDirname))
}
export function getLabelFromPath(path, state, t) {
return getLabel(
getIdentifierName(path, t),
state.opts.labelFormat,
state.file.opts.filename
)
}
const getObjPropertyLikeName = (path, t) => {
if (
(!t.isObjectProperty(path) && !t.isObjectMethod(path)) ||
path.node.computed
) {
return null
}
if (t.isIdentifier(path.node.key)) {
return path.node.key.name
}
if (t.isStringLiteral(path.node.key)) {
return path.node.key.value.replace(/\s+/g, '-')
}
return null
}
function getDeclaratorName(path, t) {
const parent = path.findParent(
p =>
p.isVariableDeclarator() ||
p.isAssignmentExpression() ||
p.isFunctionDeclaration() ||
p.isFunctionExpression() ||
p.isArrowFunctionExpression() ||
p.isObjectProperty() ||
p.isObjectMethod()
)
if (!parent) {
return ''
}
// we probably have a css call assigned to a variable
// so we'll just return the variable name
if (parent.isVariableDeclarator()) {
if (t.isIdentifier(parent.node.id)) {
return parent.node.id.name
}
return ''
}
if (parent.isAssignmentExpression()) {
let { left } = parent.node
if (t.isIdentifier(left)) {
return left.name
}
if (t.isMemberExpression(left)) {
let memberExpression = left
let name = ''
while (true) {
if (!t.isIdentifier(memberExpression.property)) {
return ''
}
name = `${memberExpression.property.name}${name ? `-${name}` : ''}`
if (t.isIdentifier(memberExpression.object)) {
return `${memberExpression.object.name}-${name}`
}
if (!t.isMemberExpression(memberExpression.object)) {
return ''
}
memberExpression = memberExpression.object
}
}
return ''
}
// we probably have an inline css prop usage
if (parent.isFunctionDeclaration()) {
return parent.node.id.name || ''
}
if (parent.isFunctionExpression()) {
if (parent.node.id) {
return parent.node.id.name || ''
}
return getDeclaratorName(parent, t)
}
if (parent.isArrowFunctionExpression()) {
return getDeclaratorName(parent, t)
}
// we could also have an object property
const objPropertyLikeName = getObjPropertyLikeName(parent, t)
if (objPropertyLikeName) {
return objPropertyLikeName
}
let variableDeclarator = parent.findParent(p => p.isVariableDeclarator())
if (!variableDeclarator || !variableDeclarator.get('id').isIdentifier()) {
return ''
}
return variableDeclarator.node.id.name
}
function getIdentifierName(path, t) {
let objPropertyLikeName = getObjPropertyLikeName(path.parentPath, t)
if (objPropertyLikeName) {
return objPropertyLikeName
}
let classOrClassPropertyParent = path.findParent(
p => t.isClassProperty(p) || t.isClass(p)
)
if (classOrClassPropertyParent) {
if (
t.isClassProperty(classOrClassPropertyParent) &&
classOrClassPropertyParent.node.computed === false &&
t.isIdentifier(classOrClassPropertyParent.node.key)
) {
return classOrClassPropertyParent.node.key.name
}
if (
t.isClass(classOrClassPropertyParent) &&
classOrClassPropertyParent.node.id
) {
return t.isIdentifier(classOrClassPropertyParent.node.id)
? classOrClassPropertyParent.node.id.name
: ''
}
}
let declaratorName = getDeclaratorName(path, t)
// if the name starts with _ it was probably generated by babel so we should ignore it
if (declaratorName.charAt(0) === '_') {
return ''
}
return declaratorName
}

153
node_modules/@emotion/babel-plugin/src/utils/minify.js generated vendored Normal file
View File

@@ -0,0 +1,153 @@
import { compile } from 'stylis'
const haveSameLocation = (element1, element2) => {
return element1.line === element2.line && element1.column === element2.column
}
const isAutoInsertedRule = element =>
element.type === 'rule' &&
element.parent &&
haveSameLocation(element, element.parent)
const toInputTree = (elements, tree) => {
for (let i = 0; i < elements.length; i++) {
const element = elements[i]
const { parent, children } = element
if (!parent) {
tree.push(element)
} else if (!isAutoInsertedRule(element)) {
parent.children.push(element)
}
if (Array.isArray(children)) {
element.children = []
toInputTree(children, tree)
}
}
return tree
}
var stringifyTree = elements => {
return elements
.map(element => {
switch (element.type) {
case 'import':
case 'decl':
return element.value
case 'comm':
// When we encounter a standard multi-line CSS comment and it contains a '@'
// character, we keep the comment. Some Stylis plugins, such as
// the stylis-rtl via the cssjanus plugin, use this special comment syntax
// to control behavior (such as: /* @noflip */). We can do this
// with standard CSS comments because they will work with compression,
// as opposed to non-standard single-line comments that will break compressed CSS.
return element.props === '/' && element.value.includes('@')
? element.value
: ''
case 'rule':
return `${element.value.replace(/&\f/g, '&')}{${stringifyTree(
element.children
)}}`
default: {
return `${element.value}{${stringifyTree(element.children)}}`
}
}
})
.join('')
}
const interleave = (strings /*: Array<*> */, interpolations /*: Array<*> */) =>
interpolations.reduce(
(array, interp, i) => array.concat([interp], strings[i + 1]),
[strings[0]]
)
function getDynamicMatches(str /*: string */) {
const re = /xxx(\d+):xxx/gm
let match
const matches = []
while ((match = re.exec(str)) !== null) {
if (match !== null) {
matches.push({
value: match[0],
p1: parseInt(match[1], 10),
index: match.index
})
}
}
return matches
}
function replacePlaceholdersWithExpressions(
str /*: string */,
expressions /*: Array<*> */,
t
) {
const matches = getDynamicMatches(str)
if (matches.length === 0) {
if (str === '') {
return []
}
return [t.stringLiteral(str)]
}
const strings = []
const finalExpressions = []
let cursor = 0
matches.forEach(({ value, p1, index }, i) => {
const preMatch = str.substring(cursor, index)
cursor = cursor + preMatch.length + value.length
if (!preMatch && i === 0) {
strings.push(t.stringLiteral(''))
} else {
strings.push(t.stringLiteral(preMatch))
}
finalExpressions.push(expressions[p1])
if (i === matches.length - 1) {
strings.push(t.stringLiteral(str.substring(index + value.length)))
}
})
return interleave(strings, finalExpressions).filter(
(node /*: { value: string } */) => {
return node.value !== ''
}
)
}
function createRawStringFromTemplateLiteral(
quasi /*: {
quasis: Array<{ value: { cooked: string } }>
} */
) {
let strs = quasi.quasis.map(x => x.value.cooked)
const src = strs
.reduce((arr, str, i) => {
arr.push(str)
if (i !== strs.length - 1) {
arr.push(`xxx${i}:xxx`)
}
return arr
}, [])
.join('')
.trim()
return src
}
export default function minify(path, t) {
const quasi = path.node.quasi
const raw = createRawStringFromTemplateLiteral(quasi)
const minified = stringifyTree(toInputTree(compile(raw), []))
const expressions = replacePlaceholdersWithExpressions(
minified,
quasi.expressions || [],
t
)
path.replaceWith(t.callExpression(path.node.tag, expressions))
}

View File

@@ -0,0 +1,39 @@
import { serializeStyles } from '@emotion/serialize'
// to anyone looking at this, this isn't intended to simplify every single case
// it's meant to simplify the most common cases so i don't want to make it especially complex
// also, this will be unnecessary when prepack is ready
export function simplifyObject(node, t /*: Object */) {
let finalString = ''
for (let i = 0; i < node.properties.length; i++) {
let property = node.properties[i]
if (
!t.isObjectProperty(property) ||
property.computed ||
(!t.isIdentifier(property.key) && !t.isStringLiteral(property.key)) ||
(!t.isStringLiteral(property.value) &&
!t.isNumericLiteral(property.value) &&
!t.isObjectExpression(property.value))
) {
return node
}
let key = property.key.name || property.key.value
if (key === 'styles') {
return node
}
if (t.isObjectExpression(property.value)) {
let simplifiedChild = simplifyObject(property.value, t)
if (!t.isStringLiteral(simplifiedChild)) {
return node
}
finalString += `${key}{${simplifiedChild.value}}`
continue
}
let value = property.value.value
finalString += serializeStyles([{ [key]: value }]).styles
}
return t.stringLiteral(finalString)
}

View File

@@ -0,0 +1,44 @@
import { SourceMapGenerator } from 'source-map'
import convert from 'convert-source-map'
function getGeneratorOpts(file) {
return file.opts.generatorOpts ? file.opts.generatorOpts : file.opts
}
export function makeSourceMapGenerator(file) {
const generatorOpts = getGeneratorOpts(file)
const filename = generatorOpts.sourceFileName
const generator = new SourceMapGenerator({
file: filename,
sourceRoot: generatorOpts.sourceRoot
})
generator.setSourceContent(filename, file.code)
return generator
}
export function getSourceMap(
offset /*: {
line: number,
column: number
} */,
state
) /*: string */ {
const generator = makeSourceMapGenerator(state.file)
const generatorOpts = getGeneratorOpts(state.file)
if (
generatorOpts.sourceFileName &&
generatorOpts.sourceFileName !== 'unknown'
) {
generator.addMapping({
generated: {
line: 1,
column: 0
},
source: generatorOpts.sourceFileName,
original: offset
})
return convert.fromObject(generator).toComment({ multiline: true })
}
return ''
}

View File

@@ -0,0 +1,60 @@
import {
getTypeScriptMakeTemplateObjectPath,
isTaggedTemplateTranspiledByBabel
} from './transpiled-output-utils'
export const appendStringReturningExpressionToArguments = (
t,
path,
expression
) => {
let lastIndex = path.node.arguments.length - 1
let last = path.node.arguments[lastIndex]
if (t.isStringLiteral(last)) {
if (typeof expression === 'string') {
path.node.arguments[lastIndex].value += expression
} else {
path.node.arguments[lastIndex] = t.binaryExpression('+', last, expression)
}
} else {
const makeTemplateObjectCallPath = getTypeScriptMakeTemplateObjectPath(path)
if (makeTemplateObjectCallPath) {
makeTemplateObjectCallPath.get('arguments').forEach(argPath => {
const elements = argPath.get('elements')
const lastElement = elements[elements.length - 1]
if (typeof expression === 'string') {
lastElement.replaceWith(
t.stringLiteral(lastElement.node.value + expression)
)
} else {
lastElement.replaceWith(
t.binaryExpression('+', lastElement.node, t.cloneNode(expression))
)
}
})
} else if (!isTaggedTemplateTranspiledByBabel(path)) {
if (typeof expression === 'string') {
path.node.arguments.push(t.stringLiteral(expression))
} else {
path.node.arguments.push(expression)
}
}
}
}
export const joinStringLiterals = (expressions /*: Array<*> */, t) => {
return expressions.reduce((finalExpressions, currentExpression, i) => {
if (!t.isStringLiteral(currentExpression)) {
finalExpressions.push(currentExpression)
} else if (
t.isStringLiteral(finalExpressions[finalExpressions.length - 1])
) {
finalExpressions[finalExpressions.length - 1].value +=
currentExpression.value
} else {
finalExpressions.push(currentExpression)
}
return finalExpressions
}, [])
}

View File

@@ -0,0 +1,144 @@
import { serializeStyles } from '@emotion/serialize'
import minify from './minify'
import { getLabelFromPath } from './label'
import { getSourceMap } from './source-maps'
import { simplifyObject } from './object-to-string'
import {
appendStringReturningExpressionToArguments,
joinStringLiterals
} from './strings'
import createNodeEnvConditional from './create-node-env-conditional'
const CSS_OBJECT_STRINGIFIED_ERROR =
"You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."
export let transformExpressionWithStyles = (
{ babel, state, path, shouldLabel, sourceMap = '' } /*: {
babel,
state,
path,
shouldLabel: boolean,
sourceMap?: string
} */
) => {
const autoLabel = state.opts.autoLabel || 'dev-only'
let t = babel.types
if (t.isTaggedTemplateExpression(path)) {
if (
!sourceMap &&
state.emotionSourceMap &&
path.node.quasi.loc !== undefined
) {
sourceMap = getSourceMap(path.node.quasi.loc.start, state)
}
minify(path, t)
}
if (t.isCallExpression(path)) {
const canAppendStrings = path.node.arguments.every(
arg => arg.type !== 'SpreadElement'
)
path.get('arguments').forEach(node => {
if (t.isObjectExpression(node)) {
node.replaceWith(simplifyObject(node.node, t))
}
})
path.node.arguments = joinStringLiterals(path.node.arguments, t)
if (
!sourceMap &&
canAppendStrings &&
state.emotionSourceMap &&
path.node.loc !== undefined
) {
sourceMap = getSourceMap(path.node.loc.start, state)
}
const label =
shouldLabel && autoLabel !== 'never'
? getLabelFromPath(path, state, t)
: null
if (
path.node.arguments.length === 1 &&
t.isStringLiteral(path.node.arguments[0])
) {
let cssString = path.node.arguments[0].value.replace(/;$/, '')
let res = serializeStyles([
`${cssString}${
label && autoLabel === 'always' ? `;label:${label};` : ''
}`
])
let prodNode = t.objectExpression([
t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)),
t.objectProperty(t.identifier('styles'), t.stringLiteral(res.styles))
])
if (!state.emotionStringifiedCssId) {
const uid = state.file.scope.generateUidIdentifier(
'__EMOTION_STRINGIFIED_CSS_ERROR__'
)
state.emotionStringifiedCssId = uid
const cssObjectToString = t.functionDeclaration(
uid,
[],
t.blockStatement([
t.returnStatement(t.stringLiteral(CSS_OBJECT_STRINGIFIED_ERROR))
])
)
cssObjectToString._compact = true
state.file.path.unshiftContainer('body', [cssObjectToString])
}
if (label && autoLabel === 'dev-only') {
res = serializeStyles([`${cssString};label:${label};`])
}
let devNode = t.objectExpression(
[
t.objectProperty(t.identifier('name'), t.stringLiteral(res.name)),
t.objectProperty(
t.identifier('styles'),
t.stringLiteral(res.styles + sourceMap)
),
t.objectProperty(
t.identifier('toString'),
t.cloneNode(state.emotionStringifiedCssId)
)
].filter(Boolean)
)
return createNodeEnvConditional(t, prodNode, devNode)
}
if (canAppendStrings && label) {
const labelString = `;label:${label};`
switch (autoLabel) {
case 'dev-only': {
const labelConditional = createNodeEnvConditional(
t,
t.stringLiteral(''),
t.stringLiteral(labelString)
)
appendStringReturningExpressionToArguments(t, path, labelConditional)
break
}
case 'always':
appendStringReturningExpressionToArguments(t, path, labelString)
break
}
}
if (sourceMap) {
let sourceMapConditional = createNodeEnvConditional(
t,
t.stringLiteral(''),
t.stringLiteral(sourceMap)
)
appendStringReturningExpressionToArguments(t, path, sourceMapConditional)
}
}
}

View File

@@ -0,0 +1,59 @@
import { createMacro } from 'babel-plugin-macros'
/*
type Transformer = Function
*/
export function createTransformerMacro(
transformers /*: { [key: string]: Transformer | [Transformer, Object] } */,
{ importSource } /*: { importSource: string } */
) {
let macro = createMacro(
({ path, source, references, state, babel, isEmotionCall }) => {
if (!path) {
path = state.file.scope.path
.get('body')
.find(p => p.isImportDeclaration() && p.node.source.value === source)
}
if (/\/macro$/.test(source)) {
path
.get('source')
.replaceWith(
babel.types.stringLiteral(source.replace(/\/macro$/, ''))
)
}
if (!isEmotionCall) {
state.emotionSourceMap = true
}
Object.keys(references).forEach(importSpecifierName => {
if (transformers[importSpecifierName]) {
references[importSpecifierName].reverse().forEach(reference => {
let options
let transformer
if (Array.isArray(transformers[importSpecifierName])) {
transformer = transformers[importSpecifierName][0]
options = transformers[importSpecifierName][1]
} else {
transformer = transformers[importSpecifierName]
options = {}
}
transformer({
state,
babel,
path,
importSource,
importSpecifierName,
options,
reference
})
})
}
})
return { keepImports: true }
}
)
macro.transformers = transformers
return macro
}

View File

@@ -0,0 +1,78 @@
// this only works correctly in modules, but we don't run on scripts anyway, so it's fine
// the difference is that in modules template objects are being cached per call site
export function getTypeScriptMakeTemplateObjectPath(path) {
if (path.node.arguments.length === 0) {
return null
}
const firstArgPath = path.get('arguments')[0]
if (
firstArgPath.isLogicalExpression() &&
firstArgPath.get('left').isIdentifier() &&
firstArgPath.get('right').isAssignmentExpression() &&
firstArgPath.get('right.right').isCallExpression() &&
firstArgPath.get('right.right.callee').isIdentifier() &&
firstArgPath.node.right.right.callee.name.includes('makeTemplateObject') &&
firstArgPath.node.right.right.arguments.length === 2
) {
return firstArgPath.get('right.right')
}
return null
}
// this is only used to prevent appending strings/expressions to arguments incorectly
// we could push them to found array expressions, as we do it for TS-transpile output ¯\_(ツ)_/¯
// it seems overly complicated though - mainly because we'd also have to check against existing stuff of a particular type (source maps & labels)
// considering Babel double-transpilation as a valid use case seems rather far-fetched
export function isTaggedTemplateTranspiledByBabel(path) {
if (path.node.arguments.length === 0) {
return false
}
const firstArgPath = path.get('arguments')[0]
if (
!firstArgPath.isCallExpression() ||
!firstArgPath.get('callee').isIdentifier()
) {
return false
}
const calleeName = firstArgPath.node.callee.name
if (!calleeName.includes('templateObject')) {
return false
}
const bindingPath = path.scope.getBinding(calleeName).path
if (!bindingPath.isFunction()) {
return false
}
const functionBody = bindingPath.get('body.body')
if (!functionBody[0].isVariableDeclaration()) {
return false
}
const declarationInit = functionBody[0].get('declarations')[0].get('init')
if (!declarationInit.isCallExpression()) {
return false
}
const declarationInitArguments = declarationInit.get('arguments')
if (
declarationInitArguments.length === 0 ||
declarationInitArguments.length > 2 ||
declarationInitArguments.some(argPath => !argPath.isArrayExpression())
) {
return false
}
return true
}