mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 14:21:29 +08:00
193 lines
5.6 KiB
TypeScript
193 lines
5.6 KiB
TypeScript
import type { OutputVar } from './types'
|
|
import { VarType } from '../../types'
|
|
import { CodeLanguage } from './types'
|
|
|
|
const splitTopLevelParams = (rawParams: string, language: CodeLanguage) => {
|
|
const params: string[] = []
|
|
let start = 0
|
|
let nesting = 0
|
|
let quote: string | undefined
|
|
let escaped = false
|
|
|
|
for (let i = 0; i < rawParams.length; i++) {
|
|
const char = rawParams[i]!
|
|
|
|
if (quote) {
|
|
if (escaped) {
|
|
escaped = false
|
|
} else if (char === '\\') {
|
|
escaped = true
|
|
} else if (rawParams.startsWith(quote, i)) {
|
|
i += quote.length - 1
|
|
quote = undefined
|
|
}
|
|
continue
|
|
}
|
|
|
|
if (char === '"' || char === "'" || (language === CodeLanguage.javascript && char === '`')) {
|
|
quote =
|
|
language === CodeLanguage.python3 && rawParams.startsWith(char.repeat(3), i)
|
|
? char.repeat(3)
|
|
: char
|
|
i += quote.length - 1
|
|
continue
|
|
}
|
|
|
|
if (char === '(' || char === '[' || char === '{') {
|
|
nesting++
|
|
} else if (char === ')' || char === ']' || char === '}') {
|
|
nesting = Math.max(0, nesting - 1)
|
|
} else if (char === ',' && nesting === 0) {
|
|
params.push(rawParams.slice(start, i))
|
|
start = i + 1
|
|
}
|
|
}
|
|
|
|
params.push(rawParams.slice(start))
|
|
return params
|
|
}
|
|
|
|
const getMainParams = (
|
|
code: string,
|
|
language: Exclude<CodeLanguage, typeof CodeLanguage.json>,
|
|
codeForSearch = code,
|
|
) => {
|
|
const patterns: Record<Exclude<CodeLanguage, typeof CodeLanguage.json>, RegExp> = {
|
|
[CodeLanguage.python3]: /def\s+main\s*\(/,
|
|
[CodeLanguage.javascript]: /function\s+main\s*\(/,
|
|
}
|
|
const match = patterns[language].exec(codeForSearch)
|
|
if (!match) return undefined
|
|
|
|
const start = match.index + match[0].length
|
|
let nesting = 1
|
|
let quote: string | undefined
|
|
let escaped = false
|
|
|
|
for (let i = start; i < code.length; i++) {
|
|
const char = code[i]!
|
|
|
|
if (quote) {
|
|
if (escaped) {
|
|
escaped = false
|
|
} else if (char === '\\') {
|
|
escaped = true
|
|
} else if (code.startsWith(quote, i)) {
|
|
i += quote.length - 1
|
|
quote = undefined
|
|
}
|
|
continue
|
|
}
|
|
|
|
if (char === '"' || char === "'" || (language === CodeLanguage.javascript && char === '`')) {
|
|
quote =
|
|
language === CodeLanguage.python3 && code.startsWith(char.repeat(3), i)
|
|
? char.repeat(3)
|
|
: char
|
|
i += quote.length - 1
|
|
} else if (char === '(') {
|
|
nesting++
|
|
} else if (char === ')' && --nesting === 0) {
|
|
return code.slice(start, i)
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
export const extractFunctionParams = (code: string, language: CodeLanguage) => {
|
|
if (language === CodeLanguage.json) return []
|
|
|
|
// Match strings first so comment markers inside them cannot consume real code.
|
|
// This is lightweight filtering, not a full lexer: quotes in JavaScript regex
|
|
// literals or nested template expressions can still confuse string boundaries.
|
|
const commentPatterns = {
|
|
[CodeLanguage.python3]:
|
|
/"""(?:\\[\s\S]|[^\\])*?"""|'''(?:\\[\s\S]|[^\\])*?'''|"(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|(#[^\r\n]*)/g,
|
|
[CodeLanguage.javascript]:
|
|
/"(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|`(?:\\[\s\S]|[^`\\])*`|(\/\/[^\r\n]*|\/\*[\s\S]*?\*\/)/g,
|
|
}
|
|
const codeWithoutComments = code.replace(
|
|
commentPatterns[language],
|
|
(match: string, comment: string | undefined) =>
|
|
comment ? comment.replace(/[^\r\n]/g, ' ') : match,
|
|
)
|
|
const codeForSignatureSearch = codeWithoutComments.replace(commentPatterns[language], (match) =>
|
|
match.replace(/[^\r\n]/g, ' '),
|
|
)
|
|
|
|
const params: string[] = []
|
|
|
|
const functionParams = getMainParams(codeWithoutComments, language, codeForSignatureSearch)
|
|
if (functionParams) {
|
|
// The JavaScript `main` is invoked as `main(inputs_obj)`, so its parameters are
|
|
// usually written as a single destructuring pattern: `function main({ arg1, arg2 })`.
|
|
const rawParams = functionParams.trim().replace(/^\{([\s\S]*)\}$/, '$1')
|
|
|
|
params.push(
|
|
...splitTopLevelParams(rawParams, language)
|
|
.map((p) => p.trim())
|
|
.filter(Boolean)
|
|
.map((p) => p.split(':')[0]!.trim()),
|
|
)
|
|
}
|
|
|
|
return params
|
|
}
|
|
export const extractReturnType = (code: string, language: CodeLanguage): OutputVar => {
|
|
const codeWithoutComments = code.replace(/\/\*\*[\s\S]*?\*\//, '')
|
|
// console.log(codeWithoutComments)
|
|
|
|
const returnIndex = codeWithoutComments.indexOf('return')
|
|
if (returnIndex === -1) return {}
|
|
|
|
// Extract the substring starting with 'return'.
|
|
const codeAfterReturn = codeWithoutComments.slice(returnIndex)
|
|
|
|
let bracketCount = 0
|
|
let startIndex = codeAfterReturn.indexOf('{')
|
|
|
|
if (language === CodeLanguage.javascript && startIndex === -1) {
|
|
const parenStart = codeAfterReturn.indexOf('(')
|
|
if (parenStart !== -1) startIndex = codeAfterReturn.indexOf('{', parenStart)
|
|
}
|
|
|
|
if (startIndex === -1) return {}
|
|
|
|
let endIndex = -1
|
|
|
|
for (let i = startIndex; i < codeAfterReturn.length; i++) {
|
|
if (codeAfterReturn[i] === '{') bracketCount++
|
|
if (codeAfterReturn[i] === '}') {
|
|
bracketCount--
|
|
if (bracketCount === 0) {
|
|
endIndex = i + 1
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (endIndex === -1) return {}
|
|
|
|
const returnContent = codeAfterReturn.slice(startIndex + 1, endIndex - 1)
|
|
// console.log(returnContent)
|
|
|
|
const result: OutputVar = {}
|
|
|
|
const keyRegex = /['"]?(\w+)['"]?\s*:(?![^{]*\})/g
|
|
const matches = returnContent.matchAll(keyRegex)
|
|
|
|
for (const match of matches) {
|
|
// console.log(`Found key: "${match[1]}" from match: "${match[0]}"`)
|
|
const key = match[1]
|
|
result[key!] = {
|
|
type: VarType.string,
|
|
children: null,
|
|
}
|
|
}
|
|
|
|
// console.log(result)
|
|
|
|
return result
|
|
}
|