feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
47
frontend/infra/idl/idl2ts-cli/src/plugins/alias.ts
Normal file
47
frontend/infra/idl/idl2ts-cli/src/plugins/alias.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type Program, on } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type IParseEntryCtx,
|
||||
isServiceDefinition,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
export class AliasPlugin {
|
||||
alias = new Map();
|
||||
|
||||
constructor(alias: Map<string, string>) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(on(HOOK.PARSE_ENTRY), this.setAlias.bind(this));
|
||||
}
|
||||
|
||||
setAlias(ctx: IParseEntryCtx) {
|
||||
ctx.ast.forEach(i => {
|
||||
if (i.isEntry) {
|
||||
i.statements.forEach(s => {
|
||||
if (isServiceDefinition(s) && this.alias.has(i.idlPath)) {
|
||||
s.name.value = this.alias.get(i.idlPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
72
frontend/infra/idl/idl2ts-cli/src/plugins/comment.ts
Normal file
72
frontend/infra/idl/idl2ts-cli/src/plugins/comment.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type IPlugin, type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import { type IParseEntryCtx } from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} content
|
||||
* @param {"CommentBlock" | "CommentLine"} type
|
||||
* @returns {{
|
||||
* value: string;
|
||||
* type: "CommentBlock" | "CommentLine";
|
||||
* }}
|
||||
*/
|
||||
function createComment(content, type = 'CommentBlock') {
|
||||
return {
|
||||
value: content,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
export class CommentPlugin implements IPlugin {
|
||||
config: { comments: string[] };
|
||||
comments: any[] = [];
|
||||
/**
|
||||
* @param {{comments: string[]}} config
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.GEN_FILE_AST), this.addComment.bind(this));
|
||||
}
|
||||
|
||||
addComment(ctx: IParseEntryCtx) {
|
||||
const { files } = ctx;
|
||||
for (const [file, res] of files.entries()) {
|
||||
if (
|
||||
res.type === 'babel' &&
|
||||
file.includes('/auto-gen/') &&
|
||||
file.endsWith('.ts')
|
||||
) {
|
||||
res.content.leadingComments = this.getComments();
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
getComments() {
|
||||
if (this.comments) {
|
||||
return this.comments;
|
||||
}
|
||||
this.comments = this.config.comments.map(i => createComment(i));
|
||||
return this.comments;
|
||||
}
|
||||
}
|
||||
230
frontend/infra/idl/idl2ts-cli/src/plugins/filter-types-plugin.ts
Normal file
230
frontend/infra/idl/idl2ts-cli/src/plugins/filter-types-plugin.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type Comment,
|
||||
type EnumDefinition,
|
||||
type FieldDefinition,
|
||||
type FieldType,
|
||||
type FunctionType,
|
||||
type IParseEntryCtx,
|
||||
type IParseResultItem,
|
||||
type Identifier,
|
||||
type ProcessIdlCtx,
|
||||
type StructDefinition,
|
||||
SyntaxType,
|
||||
type UnifyStatement,
|
||||
createFile,
|
||||
findDefinition,
|
||||
getParseResultFromNamespace,
|
||||
getStatementById,
|
||||
getValuesFromEnum,
|
||||
isBaseType,
|
||||
isEnumDefinition,
|
||||
isIdentifier,
|
||||
isListType,
|
||||
isMapType,
|
||||
isServiceDefinition,
|
||||
isSetType,
|
||||
isStructDefinition,
|
||||
parseIdFiledType,
|
||||
withExportDeclaration,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export
|
||||
import * as t from '@babel/types';
|
||||
|
||||
export class FilterTypesPlugin {
|
||||
methods: Record<string, string[]>;
|
||||
statements: Record<string, UnifyStatement[]> = {};
|
||||
enums: Record<string, EnumDefinition[]> = {};
|
||||
output: string;
|
||||
constructor(methods: Record<string, string[]>, output: string) {
|
||||
this.methods = methods;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.PARSE_ENTRY), this.filterTypes.bind(this));
|
||||
program.register(
|
||||
after(HOOK.PROCESS_IDL_AST),
|
||||
this.genEnumsFiles.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
genEnumsFiles(ctx: ProcessIdlCtx) {
|
||||
const file = createFile('');
|
||||
Object.keys(this.enums).forEach(key => {
|
||||
const defs = this.enums[key];
|
||||
const block = t.tsModuleBlock([]);
|
||||
|
||||
defs.forEach(d => {
|
||||
const values = getValuesFromEnum(d);
|
||||
const objExps = d.members.map((m, index) => {
|
||||
const valueProps = t.objectProperty(
|
||||
t.identifier('value'),
|
||||
t.numericLiteral(values[index]),
|
||||
);
|
||||
t.addComment(valueProps, 'trailing', 'm.name.value');
|
||||
return t.objectExpression([
|
||||
valueProps,
|
||||
t.objectProperty(
|
||||
t.identifier('label'),
|
||||
t.stringLiteral(m.name.value),
|
||||
),
|
||||
]);
|
||||
});
|
||||
const constNode = t.variableDeclaration('const', [
|
||||
t.variableDeclarator(
|
||||
t.identifier(d.name.value),
|
||||
t.arrayExpression(objExps),
|
||||
),
|
||||
]);
|
||||
block.body.push(withExportDeclaration(constNode));
|
||||
});
|
||||
const exportNode = withExportDeclaration(
|
||||
t.tsModuleDeclaration(t.identifier(key), block),
|
||||
);
|
||||
file.program.body.push(exportNode);
|
||||
});
|
||||
ctx.output.set(path.resolve(this.output, 'enums.ts'), {
|
||||
type: 'babel',
|
||||
content: file,
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
|
||||
filterTypes(ctx: IParseEntryCtx) {
|
||||
ctx.ast.forEach(i => {
|
||||
if (i.isEntry) {
|
||||
i.statements.forEach(s => {
|
||||
if (isServiceDefinition(s) && this.methods[s.name.value]) {
|
||||
for (const f of s.functions) {
|
||||
if (this.methods[s.name.value].includes(f.name.value)) {
|
||||
if (isIdentifier(f.returnType)) {
|
||||
this.lookupTypes(f.returnType, i);
|
||||
}
|
||||
const fieldType = f.fields[0]?.fieldType;
|
||||
if (isIdentifier(fieldType)) {
|
||||
this.lookupTypes(fieldType, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
ctx.ast = ctx.ast
|
||||
.filter(i => this.statements[i.idlPath])
|
||||
.map(i => ({ ...i, statements: this.statements[i.idlPath] }));
|
||||
return ctx;
|
||||
}
|
||||
private lookupTypes(id: Identifier, current: IParseResultItem) {
|
||||
const { namespace, refName } = parseIdFiledType(id);
|
||||
if (namespace) {
|
||||
const next = getParseResultFromNamespace(namespace, current);
|
||||
const nextID = findDefinition(next.statements, refName);
|
||||
if (nextID) {
|
||||
this.lookupTypes(nextID.name, next);
|
||||
}
|
||||
} else {
|
||||
const statement = getStatementById(id, current);
|
||||
if (statement) {
|
||||
if (this.statements[current.idlPath]) {
|
||||
if (!this.statements[current.idlPath].includes(statement)) {
|
||||
this.statements[current.idlPath].push(statement);
|
||||
}
|
||||
} else {
|
||||
this.statements[current.idlPath] = [statement];
|
||||
}
|
||||
if (isStructDefinition(statement)) {
|
||||
this.lookupStructTypes(statement, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private lookupStructTypes(
|
||||
statement: StructDefinition,
|
||||
current: IParseResultItem,
|
||||
) {
|
||||
for (const field of statement.fields) {
|
||||
const { fieldType } = field;
|
||||
this.processFiledType(fieldType, current, field);
|
||||
}
|
||||
}
|
||||
|
||||
private processFiledType(
|
||||
fieldType: FieldType | FunctionType,
|
||||
current: IParseResultItem,
|
||||
field: FieldDefinition,
|
||||
) {
|
||||
if (isBaseType(fieldType)) {
|
||||
return;
|
||||
} else if (isListType(fieldType) || isSetType(fieldType)) {
|
||||
const { valueType } = fieldType;
|
||||
return this.processFiledType(valueType, current, field);
|
||||
} else if (isMapType(fieldType)) {
|
||||
const { valueType } = fieldType;
|
||||
return this.processFiledType(valueType, current, field);
|
||||
} else if (isIdentifier(fieldType)) {
|
||||
const statement = getStatementById(fieldType, current);
|
||||
if (isEnumDefinition(statement)) {
|
||||
// 强制转位 number
|
||||
// @ts-expect-error fixme late
|
||||
fieldType.type = SyntaxType.I32Keyword;
|
||||
let namespace = current.unifyNamespace;
|
||||
const parsedFieldType = parseIdFiledType(fieldType);
|
||||
|
||||
if (parsedFieldType.namespace) {
|
||||
const next = getParseResultFromNamespace(
|
||||
parsedFieldType.namespace,
|
||||
current,
|
||||
);
|
||||
namespace = next.unifyNamespace;
|
||||
}
|
||||
const extraComment = {
|
||||
type: SyntaxType.CommentLine,
|
||||
value: `@see${fieldType.value}`,
|
||||
} as Comment;
|
||||
if (field.comments) {
|
||||
field.comments.push(extraComment);
|
||||
} else {
|
||||
field.comments = [extraComment];
|
||||
}
|
||||
if (this.enums[namespace]) {
|
||||
if (
|
||||
this.enums[namespace].some(
|
||||
i => i.name.value === statement.name.value,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.enums[namespace].push(statement);
|
||||
} else {
|
||||
this.enums[namespace] = [statement];
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
return this.lookupTypes(fieldType, current);
|
||||
}
|
||||
}
|
||||
throw new Error(`unknown type:${fieldType.type}`);
|
||||
}
|
||||
}
|
||||
83
frontend/infra/idl/idl2ts-cli/src/plugins/formatter.ts
Normal file
83
frontend/infra/idl/idl2ts-cli/src/plugins/formatter.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
|
||||
import { format } from 'prettier';
|
||||
import { type Program, on } from '@coze-arch/idl2ts-plugin';
|
||||
import { HOOK, type WriteFileCtx } from '@coze-arch/idl2ts-generator';
|
||||
interface IOption {
|
||||
path: string;
|
||||
formatter?: (content: string, filename: string) => string;
|
||||
}
|
||||
function isPromise(p: any) {
|
||||
return (
|
||||
p.then &&
|
||||
typeof p.then === 'function' &&
|
||||
typeof p.catch === 'function' &&
|
||||
typeof p.finally === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
function readConfig(file: string) {
|
||||
let config = {};
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
config = require(file);
|
||||
if (!config) {
|
||||
const content = fs.readFileSync(file, { encoding: 'utf8' });
|
||||
config = JSON.parse(content);
|
||||
}
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch
|
||||
} catch (error) {
|
||||
// just
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
export class FormatPlugin {
|
||||
private config: any;
|
||||
private formatter?: (content: string, filename: string) => string;
|
||||
constructor(op: IOption) {
|
||||
this.config = readConfig(op.path);
|
||||
this.formatter = op.formatter;
|
||||
}
|
||||
apply(program: Program) {
|
||||
program.register(on(HOOK.WRITE_FILE), this.format.bind(this));
|
||||
}
|
||||
|
||||
format(ctx: WriteFileCtx) {
|
||||
if (this.formatter) {
|
||||
ctx.content = this.formatter(ctx.content, ctx.filename);
|
||||
return ctx;
|
||||
}
|
||||
if (ctx.filename.endsWith('ts')) {
|
||||
try {
|
||||
const content = format(ctx.content, {
|
||||
...this.config,
|
||||
parser: 'typescript',
|
||||
});
|
||||
if (!isPromise(content)) {
|
||||
// @ts-expect-error fixme late
|
||||
ctx.content = content;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
61
frontend/infra/idl/idl2ts-cli/src/plugins/local-config.ts
Normal file
61
frontend/infra/idl/idl2ts-cli/src/plugins/local-config.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import { type IPlugin, type Program, after } from '@coze-arch/idl2ts-plugin';
|
||||
import { type IParseEntryCtx } from '@coze-arch/idl2ts-helper';
|
||||
import { HOOK } from '@coze-arch/idl2ts-generator';
|
||||
|
||||
interface Config {
|
||||
idlRoot: string;
|
||||
outputDir: string;
|
||||
projectRoot: string;
|
||||
}
|
||||
|
||||
export class LocalConfigPlugin implements IPlugin {
|
||||
config: Config;
|
||||
|
||||
/**
|
||||
* @param {} config
|
||||
*/
|
||||
constructor(config: Config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
apply(program: Program) {
|
||||
program.register(after(HOOK.GEN_FILE_AST), this.genLocalConfig.bind(this));
|
||||
}
|
||||
|
||||
genLocalConfig(ctx: IParseEntryCtx) {
|
||||
const mockFile = { mock: [] };
|
||||
const target = path.resolve(this.config.projectRoot, './api.dev.local.js');
|
||||
try {
|
||||
// eslint-disable-next-line security/detect-non-literal-require, @typescript-eslint/no-require-imports
|
||||
const local_config = require(target);
|
||||
mockFile.mock = local_config.mock || [];
|
||||
// eslint-disable-next-line @coze-arch/no-empty-catch, no-empty
|
||||
} catch (error) {}
|
||||
|
||||
const content = `
|
||||
module.exports = {
|
||||
mock:[${mockFile.mock.map(i => `"${i}"`).join(', ')}],
|
||||
}
|
||||
`;
|
||||
ctx.files.set(target, { type: 'text', content });
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
101
frontend/infra/idl/idl2ts-cli/src/plugins/mock-plugin.ts
Normal file
101
frontend/infra/idl/idl2ts-cli/src/plugins/mock-plugin.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { type IPlugin, type Program, before } from '@coze-arch/idl2ts-plugin';
|
||||
import {
|
||||
type IntConstant,
|
||||
isBaseType,
|
||||
SyntaxType,
|
||||
getBaseTypeConverts,
|
||||
} from '@coze-arch/idl2ts-helper';
|
||||
import { type GenMockFieldCtx, HOOK } from '@coze-arch/idl2ts-generator';
|
||||
// eslint-disable-next-line @coze-arch/no-batch-import-or-export
|
||||
import * as t from '@babel/types';
|
||||
|
||||
const NumMapper = {
|
||||
total: 1,
|
||||
code: 0,
|
||||
};
|
||||
|
||||
const StrMapper = {
|
||||
name: faker.person.lastName(),
|
||||
};
|
||||
|
||||
export class MockPlugin implements IPlugin {
|
||||
apply(program: Program) {
|
||||
program.register(before(HOOK.GEN_MOCK_FILED), this.genMockValue.bind(this));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
genMockValue = (ctx: GenMockFieldCtx) => {
|
||||
const { context, fieldType, defaultValue } = ctx;
|
||||
if (isBaseType(fieldType)) {
|
||||
const type = getBaseTypeConverts('number')[fieldType.type];
|
||||
|
||||
if (type === 'string') {
|
||||
let value = faker.word.words();
|
||||
if (defaultValue && defaultValue.type === SyntaxType.StringLiteral) {
|
||||
value = (defaultValue as any).value;
|
||||
}
|
||||
if (context) {
|
||||
const { fieldDefinition } = context;
|
||||
const fieldName = fieldDefinition.name.value;
|
||||
// 各类 ID
|
||||
if (fieldName.toLocaleUpperCase().endsWith('ID')) {
|
||||
value = String(faker.number.int());
|
||||
}
|
||||
// email 处理
|
||||
if (fieldName.includes('Email')) {
|
||||
value = `${faker.person.lastName()}@foo.com`;
|
||||
}
|
||||
// 直接映射值
|
||||
value = StrMapper[fieldName] || value;
|
||||
}
|
||||
ctx.output = t.stringLiteral(value);
|
||||
} else if (type === 'number') {
|
||||
let value = faker.number.int({ min: 0, max: 10000 });
|
||||
if (defaultValue && defaultValue.type === SyntaxType.IntConstant) {
|
||||
value = Number((defaultValue as IntConstant).value.value);
|
||||
}
|
||||
if (context) {
|
||||
const { fieldDefinition } = context;
|
||||
const fieldName = fieldDefinition.name.value;
|
||||
const formatName = fieldName.toLocaleUpperCase();
|
||||
// 各类 ID
|
||||
if (formatName.endsWith('ID')) {
|
||||
value = faker.number.int();
|
||||
}
|
||||
// 时间戳
|
||||
if (formatName.endsWith('TIME') || formatName.includes('TIMESTAMP')) {
|
||||
value = dayjs(faker.date.anytime()).valueOf();
|
||||
}
|
||||
// 类型状态
|
||||
if (formatName.endsWith('STATUS') || formatName.includes('TYPE')) {
|
||||
value = faker.number.int({ min: 0, max: 1 });
|
||||
}
|
||||
|
||||
// 直接映射值
|
||||
const mapVal = NumMapper[fieldName];
|
||||
value = typeof mapVal !== 'undefined' ? mapVal : value;
|
||||
}
|
||||
ctx.output = t.numericLiteral(value);
|
||||
}
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user