feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
/*
* 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 * as path from 'path';
import * as fs from 'fs';
import { logAndThrowError , mergeObject, getPosixPath } from '../utils';
import { parseThriftContent } from './thrift';
import { parseProtoContent } from './proto';
import { type UnifyDocument } from './type';
type FileType = 'thrift' | 'proto';
// export statements
export * from './type';
export interface ParseOption {
root?: string;
namespaceRefer?: boolean;
cache?: boolean;
ignoreGoTag?: boolean;
ignoreGoTagDash?: boolean;
preproccess?: (param: { content: string; path?: string }) => string;
searchPaths?: string[];
}
const parseOptionDefault: ParseOption = {
root: '.',
namespaceRefer: true,
cache: false,
ignoreGoTag: false,
ignoreGoTagDash: false,
searchPaths: [],
};
// the key of fileContentMap should be absolute path
export function parse(
filePath: string,
option: ParseOption = {},
fileContentMap?: Record<string, string>,
): UnifyDocument {
const {
root,
namespaceRefer,
cache,
ignoreGoTag,
ignoreGoTagDash,
preproccess,
searchPaths,
} = mergeObject(parseOptionDefault, option) as Required<ParseOption>;
const fullRootDir = getPosixPath(path.resolve(process.cwd(), root));
let fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
let idlFileType: FileType = 'thrift';
let content = '';
if (/\.thrift$/.test(filePath)) {
fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
if (fileContentMap) {
content = fileContentMap[filePath];
if (typeof content === 'undefined') {
logAndThrowError(`file "${filePath}" does not exist in fileContentMap`);
}
} else {
if (!fs.existsSync(fullFilePath)) {
const message = `no such file: ${fullFilePath}`;
logAndThrowError(message);
}
content = fs.readFileSync(fullFilePath, 'utf8');
}
} else if (/\.proto$/.test(filePath)) {
idlFileType = 'proto';
fullFilePath = getPosixPath(path.resolve(fullRootDir, filePath));
if (fileContentMap) {
content = fileContentMap[filePath];
if (typeof content === 'undefined') {
logAndThrowError(`file "${filePath}" does not exist in fileContentMap`);
}
} else {
if (!fs.existsSync(fullFilePath)) {
const message = `no such file: ${fullFilePath}`;
logAndThrowError(message);
}
content = fs.readFileSync(fullFilePath, 'utf8');
}
} else {
const message = `invalid filePath: "${filePath}"`;
logAndThrowError(message);
}
const absoluteFilePath = getPosixPath(
path.relative(fullRootDir, fullFilePath),
);
if (typeof preproccess === 'function') {
content = preproccess({ content, path: absoluteFilePath });
}
if (idlFileType === 'thrift') {
const looseAbsoluteFilePath = absoluteFilePath.replace(/\.thrift$/, '');
const document = parseThriftContent(
content,
{
loosePath: looseAbsoluteFilePath,
rootDir: fullRootDir,
namespaceRefer,
cache,
ignoreGoTag,
ignoreGoTagDash,
searchPaths,
},
fileContentMap,
);
return document;
}
const looseAbsoluteFilePath = absoluteFilePath.replace(/\.proto$/, '');
const document = parseProtoContent(
content,
{
loosePath: looseAbsoluteFilePath,
rootDir: fullRootDir,
cache,
searchPaths,
},
fileContentMap,
);
return document;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,712 @@
/*
* 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.
*/
/* eslint no-param-reassign: ["error", { "props": false }], import/prefer-default-export: off */
import * as path from 'path';
import * as fs from 'fs';
import * as t from '@lancewuz/thrift-parser';
import { logAndThrowError, getPosixPath } from '../utils';
import * as extensionUtil from '../common/extension_util';
import { convertIntToString } from './util';
import {
SyntaxType,
type Annotation,
type ServiceDefinition,
type FunctionDefinition,
type EnumDefinition,
type ExtensionConfig,
type FieldExtensionConfig,
type ServiceExtensionConfig,
type FunctionExtensionConfig,
type Comment,
type Identifier,
type ContainerType,
type MapType,
type StructDefinition,
type TypedefDefinition,
type TextLocation,
type FunctionType,
type FieldType,
type UnifyStatement,
type Annotations,
type EnumMember,
type UnifyDocument,
type FieldDefinition,
type ConstDefinition,
type ConstValue,
} from './type';
// cache parsed document
const fileDocumentMap: Record<string, t.ThriftDocument> = {};
const namespaceDefault = 'root';
let enumNames: string[] = [];
let cache = true;
let absoluteFileContentMap: Record<string, string> | undefined;
let rootDir = '';
let entryLooseAbsoluteFilePath = '';
let ignoreGoTag = false;
let ignoreGoTagDash = false;
let addNamespaceValue: ((fieldType: FunctionType) => void) | undefined;
let searchPaths: string[] = [];
const mergeConfig = (
config: Partial<ExtensionConfig>,
config2: Partial<ExtensionConfig>,
): ExtensionConfig => {
const mergedTags: string[] = [];
if (config?.tag) {
mergedTags.push(config.tag);
}
if (config2?.tag) {
mergedTags.push(config2.tag);
}
const res = Object.assign(config, config2);
if (mergedTags.length > 0) {
res.tag = mergedTags.join(',');
}
return res;
};
function extractExtensionConfigFromAnnotation(
annotation: Annotation,
): false | ExtensionConfig {
let key = annotation.name.value;
const value = (annotation.value && annotation.value.value) || '';
if (/^((agw\.)|(api\.)|(go\.tag))/.test(key) === false) {
return false;
}
if (/^((agw\.)|(api\.))/.test(key)) {
key = key.slice(4);
}
const config = extensionUtil.extractExtensionConfig(key, value, ignoreGoTag);
return config;
}
function getFieldExtensionConfig(
fieldName: string,
fieldType: FunctionType,
annotations?: Annotations,
): FieldExtensionConfig {
if (!annotations) {
return {};
}
const extensionConfig: FieldExtensionConfig = {};
for (const annotation of annotations.annotations) {
const config = extractExtensionConfigFromAnnotation(annotation);
if (config) {
if ('key' in config) {
// a key which is the same with the field name will make no sense
if (config.key === fieldName) {
delete config.key;
}
} else if (config.position === 'path') {
if (
[
SyntaxType.DoubleKeyword,
SyntaxType.BoolKeyword,
SyntaxType.ByteKeyword,
SyntaxType.ListKeyword,
SyntaxType.SetKeyword,
SyntaxType.MapKeyword,
SyntaxType.VoidKeyword,
].includes(fieldType.type)
) {
const fullFilePath = path.resolve(
rootDir,
`${entryLooseAbsoluteFilePath}.proto`,
);
const message = `path parameter '${fieldName}' should be string or integer`;
const fullMessage = `${message} (${fullFilePath})`;
logAndThrowError(fullMessage, message);
}
}
mergeConfig(extensionConfig, config);
}
}
const res = extensionUtil.filterFieldExtensionConfig(extensionConfig);
return res;
}
function getFuncExtensionConfig(
annotations?: Annotations,
): FunctionExtensionConfig {
if (!annotations) {
return {};
}
const extensionConfig: FunctionExtensionConfig = {};
for (const annotation of annotations.annotations) {
const config = extractExtensionConfigFromAnnotation(annotation);
/* istanbul ignore next */
if (config) {
Object.assign(extensionConfig, config);
}
}
return extensionUtil.filterFunctionExtensionConfig(extensionConfig);
}
function getServiceExtensionConfig(
annotations?: Annotations,
): ServiceExtensionConfig {
if (!annotations) {
return {};
}
const extensionConfig: ServiceExtensionConfig = {};
for (const annotation of annotations.annotations) {
const config = extractExtensionConfigFromAnnotation(annotation);
/* istanbul ignore else */
if (config) {
Object.assign(extensionConfig, config);
}
}
return extensionUtil.filterServiceExtensionConfig(extensionConfig);
}
function reviseFieldComments(fields: (FieldDefinition | EnumMember)[]): void {
/* istanbul ignore next */
if (fields.length < 2) {
return;
}
// move previous comments to current field
for (let i = fields.length - 1; i > 0; i--) {
const currentField = fields[i];
const prevField = fields[i - 1];
const prevFieldEndLine = (prevField.loc as TextLocation).end.line;
const prevFieldComments = prevField.comments;
for (let j = 0; j < prevFieldComments.length; j++) {
if (
(prevFieldComments[j].loc as TextLocation).end.line > prevFieldEndLine
) {
const dislocatedComments = prevFieldComments.splice(
j,
prevFieldComments.length - j,
);
currentField.comments = [
...dislocatedComments,
...currentField.comments,
];
break;
}
}
}
// move next comments to current field
for (let i = 0; i < fields.length - 1; i++) {
const currentField = fields[i];
const nextField = fields[i + 1];
const currentFieldEndLine = (currentField.loc as TextLocation).end.line;
const nextFieldFirstComment = nextField.comments[0];
if (
nextFieldFirstComment &&
(nextFieldFirstComment.loc as TextLocation).end.line ===
currentFieldEndLine
) {
const dislocatedComment = nextField.comments.shift() as Comment;
currentField.comments.push(dislocatedComment);
}
}
}
function reviseFuncComments(functions: FunctionDefinition[]): void {
if (functions.length < 2) {
return;
}
for (let i = 0; i < functions.length - 1; i++) {
const currentFunc = functions[i];
const nextFunc = functions[i + 1];
const currentFuncEndLine = (currentFunc.loc as TextLocation).end.line;
const nextFuncFirstComment = nextFunc.comments[0];
if (
nextFuncFirstComment &&
(nextFuncFirstComment.loc as TextLocation).end.line === currentFuncEndLine
) {
const dislocatedComment = nextFunc.comments.shift() as Comment;
currentFunc.comments.push(dislocatedComment);
}
}
}
function getUnifyNamespace(
looseAbsoluteFilePath: string,
astNamespaces?: t.NamespaceDefinition[],
): {
namespace: string;
unifyNamespace: string;
} {
let namespace = '';
let unifyNamespace = '';
if (astNamespaces && astNamespaces.length > 0) {
const namespaceMap: Record<string, t.NamespaceDefinition> = {};
for (const astNamespace of astNamespaces) {
const scopeName = astNamespace.scope.value;
namespaceMap[scopeName] = astNamespace;
}
const astNamespaceCurrent =
namespaceMap.js || namespaceMap.go || namespaceMap.py;
if (astNamespaceCurrent) {
namespace = astNamespaceCurrent.name.value;
unifyNamespace = namespace;
} else if (namespaceMap.java) {
namespace = namespaceMap.java.name.value.split('.').pop() as string;
unifyNamespace = namespace;
} else {
const message = 'a js namespace should be specifed';
const fullFilePath = path.resolve(
rootDir,
`${looseAbsoluteFilePath}.thrift`,
);
const infoMessage = `${message} (${fullFilePath})`;
logAndThrowError(infoMessage, message);
}
} else {
namespace = '';
unifyNamespace = namespaceDefault;
}
unifyNamespace = unifyNamespace
.replace(/\./g, '_')
.replace(/[^a-zA-Z0-9_]/g, '');
return { namespace, unifyNamespace };
}
function createAddNamespaceReferValue(
filenameNamespaceMap: Record<string, string>,
namespace: string,
): (fieldType: FunctionType) => void {
const regExpNamespaceMap = new Map<RegExp, string>();
Object.keys(filenameNamespaceMap).forEach(filename => {
const regExp = new RegExp(`^${filename}(\\.[^\\.]*)$`);
regExpNamespaceMap.set(regExp, filenameNamespaceMap[filename]);
});
function addNamespaceReferValue(fieldType: FunctionType): void {
if ((fieldType as Identifier).type === SyntaxType.Identifier) {
const identifierValue = (fieldType as Identifier).value;
if (!identifierValue.includes('.')) {
(fieldType as Identifier).namespaceValue =
`${namespace}.${identifierValue}`;
} else {
const parts = identifierValue.split('.');
if (parts.length === 2 && enumNames.includes(parts[0])) {
(fieldType as Identifier).namespaceValue =
`${namespace}.${identifierValue}`;
} else {
for (const regExp of regExpNamespaceMap.keys()) {
if (regExp.test(identifierValue)) {
const namespaceName = regExpNamespaceMap.get(regExp);
(fieldType as Identifier).namespaceValue =
identifierValue.replace(regExp, `${namespaceName}$1`);
break;
}
}
}
}
} else if ((fieldType as ContainerType).valueType) {
addNamespaceReferValue(
(fieldType as ContainerType).valueType as FunctionType,
);
if ((fieldType as MapType).keyType) {
addNamespaceReferValue((fieldType as MapType).keyType as FunctionType);
}
}
}
return addNamespaceReferValue;
}
function getAddNamespaceReferValue(
astIncludes: t.IncludeDefinition[],
namespace: string,
): ((fieldType: FunctionType) => void) | undefined {
const filenameNamespaceMap: Record<string, string> = {};
for (const astInclude of astIncludes) {
const idlFilePath = astInclude.path.value;
const looseIdlFilePath = idlFilePath.replace(/\.thrift$/, '');
const looseFilename = looseIdlFilePath.split('/').pop() as string;
// try relative path
let looseAbsoluteFilePath = getPosixPath(
path.join(path.dirname(entryLooseAbsoluteFilePath), looseIdlFilePath),
);
// try absulote path
const alternativeLooseAbsoluteFilePath = looseIdlFilePath;
let document =
fileDocumentMap[looseAbsoluteFilePath] ||
fileDocumentMap[alternativeLooseAbsoluteFilePath];
if (!document) {
let content = '';
if (absoluteFileContentMap) {
content = absoluteFileContentMap[`${looseAbsoluteFilePath}.thrift`];
if (typeof content === 'undefined') {
content =
absoluteFileContentMap[
`${alternativeLooseAbsoluteFilePath}.thrift`
];
if (typeof content === 'undefined') {
logAndThrowError(
`file ${looseAbsoluteFilePath}.thrift does not exist in fileContentMap`,
);
}
looseAbsoluteFilePath = alternativeLooseAbsoluteFilePath;
}
} else {
let fullFilePath = getPosixPath(
path.resolve(rootDir, `${looseAbsoluteFilePath}.thrift`),
);
// Search
if (!fs.existsSync(fullFilePath)) {
const filePaths = [rootDir, ...searchPaths].map(searchPath =>
getPosixPath(
path.resolve(
rootDir,
searchPath,
`${alternativeLooseAbsoluteFilePath}.thrift`,
),
),
);
const existedFilePath = filePaths.find(filePath =>
fs.existsSync(filePath),
);
if (typeof existedFilePath === 'undefined') {
logAndThrowError(`file ${filePaths[0]} does not exist`);
} else {
fullFilePath = existedFilePath;
looseAbsoluteFilePath = path
.relative(rootDir, existedFilePath)
.replace(/\.thrift$/, '');
}
}
content = fs.readFileSync(fullFilePath, 'utf8');
}
document = parseContent(content, looseAbsoluteFilePath);
} else if (!fileDocumentMap[looseAbsoluteFilePath]) {
looseAbsoluteFilePath = alternativeLooseAbsoluteFilePath;
}
const astNamespaces: t.NamespaceDefinition[] = [];
for (const statement of document.body) {
if (statement.type === t.SyntaxType.NamespaceDefinition) {
astNamespaces.push(statement as t.NamespaceDefinition);
}
}
const { unifyNamespace } = getUnifyNamespace(
looseAbsoluteFilePath,
astNamespaces,
);
filenameNamespaceMap[looseFilename] = unifyNamespace;
}
return createAddNamespaceReferValue(filenameNamespaceMap, namespace);
}
function convertTypedefDefinition(
astTypedef: TypedefDefinition,
): TypedefDefinition {
const typedefDefinition: TypedefDefinition = { ...astTypedef };
// const { name, definitionType} = typedefDefinition
if (typeof addNamespaceValue === 'function') {
addNamespaceValue(typedefDefinition.definitionType);
addNamespaceValue(typedefDefinition.name);
}
return typedefDefinition;
}
function addNamespaceValueToConstValue(constValue: ConstValue) {
if (typeof addNamespaceValue === 'function') {
if (constValue.type === SyntaxType.Identifier) {
addNamespaceValue(constValue);
} else if (constValue.type === SyntaxType.ConstMap) {
for (const property of constValue.properties) {
addNamespaceValueToConstValue(property.name);
addNamespaceValueToConstValue(property.initializer);
}
} else if (constValue.type === SyntaxType.ConstList) {
for (const element of constValue.elements) {
addNamespaceValueToConstValue(element);
}
}
}
}
function convertConstDefinition(astConst: ConstDefinition): ConstDefinition {
const constDefinition: ConstDefinition = { ...astConst };
if (typeof addNamespaceValue === 'function') {
addNamespaceValue(constDefinition.name);
addNamespaceValue(constDefinition.fieldType);
addNamespaceValueToConstValue(constDefinition.initializer);
}
return constDefinition;
}
function convertStructDefinition(
astStruct: StructDefinition,
): StructDefinition {
const structDefinition: StructDefinition = { ...astStruct };
const { name, fields } = structDefinition;
const newName = name;
if (addNamespaceValue) {
addNamespaceValue(newName);
}
for (const field of fields) {
const { fieldType, annotations } = field;
const fieldName = field.name.value;
if (addNamespaceValue) {
addNamespaceValue(fieldType);
}
field.extensionConfig = getFieldExtensionConfig(
fieldName,
fieldType,
annotations,
);
if ((field.extensionConfig.tag || '').includes('omitempty')) {
field.requiredness = 'optional';
}
}
reviseFieldComments(fields);
const newFields: FieldDefinition[] = [];
for (const field of fields) {
const tag = (field.extensionConfig && field.extensionConfig.tag) || '';
if (tag.includes('int2str')) {
field.fieldType = convertIntToString(field.fieldType as FieldType);
}
if (ignoreGoTagDash || !tag.includes('ignore')) {
newFields.push(field);
}
}
structDefinition.fields = newFields;
structDefinition.name = newName;
return structDefinition;
}
function convertEnumDefinition(astEnum: EnumDefinition): EnumDefinition {
const enumDefinition: EnumDefinition = { ...astEnum };
const { name, members } = enumDefinition;
enumNames.push(name.value);
reviseFieldComments(members);
if (addNamespaceValue) {
addNamespaceValue(name);
}
return enumDefinition;
}
function convertFunctionDefinition(
astFunc: FunctionDefinition,
): FunctionDefinition {
const functionDefinition: FunctionDefinition = { ...astFunc };
const { returnType, fields, annotations, name } = functionDefinition;
if (addNamespaceValue) {
addNamespaceValue(name);
addNamespaceValue(returnType);
for (const field of fields) {
addNamespaceValue(field.fieldType);
}
}
functionDefinition.extensionConfig = getFuncExtensionConfig(annotations);
return functionDefinition;
}
function convertServiceDefinition(
astService: ServiceDefinition,
): ServiceDefinition {
const serviceDefinition: ServiceDefinition = { ...astService };
const { annotations, name } = serviceDefinition;
const functions: FunctionDefinition[] = [];
for (const astFunc of astService.functions) {
functions.push(convertFunctionDefinition(astFunc));
}
if (addNamespaceValue) {
addNamespaceValue(name);
}
reviseFuncComments(functions);
serviceDefinition.functions = functions;
serviceDefinition.extensionConfig = getServiceExtensionConfig(annotations);
return serviceDefinition;
}
function parseContent(
content: string,
looseAbsoluteFilePath: string,
): t.ThriftDocument {
if (fileDocumentMap[looseAbsoluteFilePath]) {
return fileDocumentMap[looseAbsoluteFilePath];
}
const document: t.ThriftDocument | t.ThriftErrors = t.parse(content);
if ((document as t.ThriftErrors).type === t.SyntaxType.ThriftErrors) {
const error = (document as t.ThriftErrors).errors[0];
const { start } = error.loc;
const fullFilePath = path.resolve(
rootDir,
`${looseAbsoluteFilePath}.thrift`,
);
const message = `${error.message}(${fullFilePath}:${start.line}:${start.column})`;
logAndThrowError(message, error.message);
}
if (cache) {
fileDocumentMap[looseAbsoluteFilePath] = document as t.ThriftDocument;
}
return document as t.ThriftDocument;
}
export function parseThriftContent(
content: string,
option: {
loosePath: string;
rootDir: string;
cache: boolean;
searchPaths: string[];
namespaceRefer: boolean;
ignoreGoTag: boolean;
ignoreGoTagDash: boolean;
},
fileContentMap?: Record<string, string>,
): UnifyDocument {
rootDir = option.rootDir;
entryLooseAbsoluteFilePath = option.loosePath;
cache = option.cache;
ignoreGoTag = option.ignoreGoTag;
ignoreGoTagDash = option.ignoreGoTagDash;
searchPaths = option.searchPaths;
absoluteFileContentMap = fileContentMap;
addNamespaceValue = undefined;
enumNames = [];
// parse file content
const document = parseContent(content, entryLooseAbsoluteFilePath);
const statementGroup: Record<string, t.ThriftStatement[]> = {};
for (const statement of (document as t.ThriftDocument).body) {
let { type } = statement;
// NOTE: in the latest version of Thrift, union is similar to struct except that all fields are converted to 'optional'.
// the idl parse shields the difference, so we can dispose them together.
if (type === t.SyntaxType.UnionDefinition) {
type = t.SyntaxType.StructDefinition;
statement.type = t.SyntaxType.StructDefinition;
}
if (!statementGroup[type]) {
statementGroup[type] = [statement];
} else {
statementGroup[type].push(statement);
}
}
const { unifyNamespace, namespace } = getUnifyNamespace(
entryLooseAbsoluteFilePath,
statementGroup[t.SyntaxType.NamespaceDefinition] as t.NamespaceDefinition[],
);
if (option.namespaceRefer) {
addNamespaceValue = getAddNamespaceReferValue(
(statementGroup[t.SyntaxType.IncludeDefinition] ||
[]) as t.IncludeDefinition[],
unifyNamespace,
);
}
const statements: UnifyStatement[] = [];
if (statementGroup[t.SyntaxType.TypedefDefinition]) {
for (const astTypedef of statementGroup[t.SyntaxType.TypedefDefinition]) {
statements.push(convertTypedefDefinition(astTypedef as any));
}
}
if (statementGroup[SyntaxType.EnumDefinition]) {
for (const astEnum of statementGroup[t.SyntaxType.EnumDefinition]) {
statements.push(convertEnumDefinition(astEnum as any));
}
}
if (statementGroup[t.SyntaxType.ConstDefinition]) {
for (const astConst of statementGroup[t.SyntaxType.ConstDefinition]) {
statements.push(convertConstDefinition(astConst as any));
}
}
if (statementGroup[SyntaxType.StructDefinition]) {
for (const astStruct of statementGroup[t.SyntaxType.StructDefinition]) {
statements.push(convertStructDefinition(astStruct as any));
}
}
if (statementGroup[SyntaxType.ServiceDefinition]) {
for (const astService of statementGroup[t.SyntaxType.ServiceDefinition]) {
statements.push(convertServiceDefinition(astService as any));
}
}
const includes: string[] = [];
if (statementGroup[t.SyntaxType.IncludeDefinition]) {
for (const astInclude of statementGroup[t.SyntaxType.IncludeDefinition]) {
includes.push((astInclude as t.IncludeDefinition).path.value);
}
}
const unifyDocument: UnifyDocument = {
type: SyntaxType.UnifyDocument,
namespace,
unifyNamespace,
includes,
statements,
includeRefer: {},
};
return unifyDocument;
}

View File

@@ -0,0 +1,407 @@
/*
* 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 FieldExtensionConfig,
type ServiceExtensionConfig,
type FunctionExtensionConfig,
} from '../common/extension_type';
export * from '../common/extension_type';
export interface Node {
type: SyntaxType;
}
export interface SyntaxNode extends Node {
loc?: TextLocation;
}
export interface StructLike {
name: Identifier;
fields: Array<FieldDefinition>;
annotations?: Annotations;
comments: Array<Comment>;
loc: TextLocation;
}
export interface TextLocation {
start: TextPosition;
end: TextPosition;
}
export interface TextPosition {
line: number;
column: number;
index: number;
}
export interface Token extends SyntaxNode {
text: string;
}
export interface UnifyDocument extends Node {
type: SyntaxType.UnifyDocument;
statements: UnifyStatement[];
namespace: string;
unifyNamespace: string;
includes: string[];
includeRefer: Record<string, string>;
}
export type UnifyStatement =
| EnumDefinition
| StructDefinition
| TypedefDefinition
| ConstDefinition
| ServiceDefinition;
export type CommentType = SyntaxType.CommentLine | SyntaxType.CommentBlock;
export type Comment = CommentLine | CommentBlock;
export interface CommentLine extends SyntaxNode {
type: SyntaxType.CommentLine;
value: string;
}
export interface CommentBlock extends SyntaxNode {
type: SyntaxType.CommentBlock;
value: Array<string>;
}
export interface Annotations extends SyntaxNode {
annotations: Array<Annotation>;
}
export interface Annotation extends SyntaxNode {
name: Identifier;
value?: StringLiteral;
}
export interface PrimarySyntax extends SyntaxNode {
comments: Array<Comment>;
}
export type FieldType = BaseType | ContainerType | Identifier;
export type FunctionType = FieldType | VoidType;
export type KeywordType =
| SyntaxType.StringKeyword
| SyntaxType.DoubleKeyword
| SyntaxType.BoolKeyword
| SyntaxType.I8Keyword
| SyntaxType.I16Keyword
| SyntaxType.I32Keyword
| SyntaxType.I64Keyword
| SyntaxType.BinaryKeyword
| SyntaxType.ByteKeyword;
export interface VoidType extends SyntaxNode {
type: SyntaxType.VoidKeyword;
}
export type ContainerType = SetType | MapType | ListType;
export interface BaseType extends SyntaxNode {
type: KeywordType;
annotations?: Annotations;
}
export interface SetType extends SyntaxNode {
type: SyntaxType.SetType;
valueType: FieldType;
annotations?: Annotations;
}
export interface ListType extends SyntaxNode {
type: SyntaxType.ListType;
valueType: FieldType;
annotations?: Annotations;
}
export interface MapType extends SyntaxNode {
type: SyntaxType.MapType;
keyType: FieldType;
valueType: FieldType;
annotations?: Annotations;
}
export type ConstValue =
| StringLiteral
| IntConstant
| DoubleConstant
| BooleanLiteral
| ConstMap
| ConstList
| Identifier;
export interface ConstDefinition extends PrimarySyntax {
type: SyntaxType.ConstDefinition;
name: Identifier;
fieldType: FieldType;
initializer: ConstValue;
annotations?: Annotations;
}
export type FieldRequired = 'required' | 'optional';
export interface InterfaceWithFields extends PrimarySyntax {
name: Identifier;
fields: Array<FieldDefinition>;
annotations?: Annotations;
options?: Record<string, string>;
nested?: Record<string, EnumDefinition | StructDefinition>;
}
export interface StructDefinition extends InterfaceWithFields {
type: SyntaxType.StructDefinition;
}
export interface FieldDefinition extends PrimarySyntax {
type: SyntaxType.FieldDefinition;
name: Identifier;
fieldID: FieldID | null;
fieldType: FunctionType;
requiredness?: FieldRequired | null;
defaultValue?: ConstValue | null;
annotations?: Annotations;
options?: Record<string, string>;
extensionConfig?: FieldExtensionConfig;
}
export interface FieldID extends SyntaxNode {
type: SyntaxType.FieldID;
value: number;
}
export interface EnumDefinition extends PrimarySyntax {
type: SyntaxType.EnumDefinition;
name: Identifier;
members: Array<EnumMember>;
annotations?: Annotations;
options?: Record<string, string>;
}
export interface EnumMember extends PrimarySyntax {
type: SyntaxType.EnumMember;
name: Identifier;
initializer?: IntConstant | null;
annotations?: Annotations;
}
export interface TypedefDefinition extends PrimarySyntax {
type: SyntaxType.TypedefDefinition;
name: Identifier;
definitionType: FieldType;
annotations?: Annotations;
}
export interface ServiceDefinition extends PrimarySyntax {
type: SyntaxType.ServiceDefinition;
name: Identifier;
extends?: Identifier | null;
functions: Array<FunctionDefinition>;
annotations?: Annotations;
options?: Record<string, string>;
extensionConfig?: ServiceExtensionConfig;
}
export interface FunctionDefinition extends PrimarySyntax {
type: SyntaxType.FunctionDefinition;
name: Identifier;
oneway: boolean;
returnType: FunctionType;
fields: Array<FieldDefinition>;
throws: Array<FieldDefinition>;
modifiers: Array<Token>;
annotations?: Annotations;
options?: Record<string, string>;
extensionConfig?: FunctionExtensionConfig;
}
export interface ParametersDefinition extends SyntaxNode {
type: SyntaxType.ParametersDefinition;
fields: Array<FieldDefinition>;
}
export interface StringLiteral extends SyntaxNode {
type: SyntaxType.StringLiteral;
value: string;
}
export interface BooleanLiteral extends SyntaxNode {
type: SyntaxType.BooleanLiteral;
value: boolean;
}
export interface IntegerLiteral extends SyntaxNode {
type: SyntaxType.IntegerLiteral;
value: string;
}
export interface HexLiteral extends SyntaxNode {
type: SyntaxType.HexLiteral;
value: string;
}
export interface FloatLiteral extends SyntaxNode {
type: SyntaxType.FloatLiteral;
value: string;
}
export interface ExponentialLiteral extends SyntaxNode {
type: SyntaxType.ExponentialLiteral;
value: string;
}
export interface IntConstant extends SyntaxNode {
type: SyntaxType.IntConstant;
value: IntegerLiteral | HexLiteral;
}
export interface DoubleConstant extends SyntaxNode {
type: SyntaxType.DoubleConstant;
value: FloatLiteral | ExponentialLiteral;
}
export interface ConstMap extends SyntaxNode {
type: SyntaxType.ConstMap;
properties: Array<PropertyAssignment>;
}
export interface ConstList extends SyntaxNode {
type: SyntaxType.ConstList;
elements: Array<ConstValue>;
}
export interface PropertyAssignment extends SyntaxNode {
type: SyntaxType.PropertyAssignment;
name: ConstValue;
initializer: ConstValue;
}
export interface Identifier extends SyntaxNode {
type: SyntaxType.Identifier;
value: string;
// value with one level namespace
namespaceValue?: string;
annotations?: Annotations;
}
export enum SyntaxType {
UnifyDocument = 'UnifyDocument',
Identifier = 'Identifier',
FieldID = 'FieldID',
// Statements
ConstDefinition = 'ConstDefinition',
StructDefinition = 'StructDefinition',
EnumDefinition = 'EnumDefinition',
ServiceDefinition = 'ServiceDefinition',
TypedefDefinition = 'TypedefDefinition',
// Fields
FieldDefinition = 'FieldDefinition',
FunctionDefinition = 'FunctionDefinition',
ParametersDefinition = 'ParametersDefinition',
// Type Annotations
FieldType = 'FieldType',
BaseType = 'BaseType',
SetType = 'SetType',
MapType = 'MapType',
ListType = 'ListType',
// Values
ConstValue = 'ConstValue',
IntConstant = 'IntConstant',
DoubleConstant = 'DoubleConstant',
ConstList = 'ConstList',
ConstMap = 'ConstMap',
EnumMember = 'EnumMember',
// Literals
CommentLine = 'CommentLine',
CommentBlock = 'CommentBlock',
StringLiteral = 'StringLiteral',
IntegerLiteral = 'IntegerLiteral',
FloatLiteral = 'FloatLiteral',
HexLiteral = 'HexLiteral',
ExponentialLiteral = 'ExponentialLiteral',
BooleanLiteral = 'BooleanLiteral',
PropertyAssignment = 'PropertyAssignment',
// Tokens
LeftParenToken = 'LeftParenToken',
RightParenToken = 'RightParenToken',
LeftBraceToken = 'LeftBraceToken',
RightBraceToken = 'RightBraceToken',
LeftBracketToken = 'LeftBracketToken',
RightBracketToken = 'RightBracketToken',
CommaToken = 'CommaToken',
DotToken = 'DotToken',
MinusToken = 'MinusToken',
SemicolonToken = 'SemicolonToken',
ColonToken = 'ColonToken',
StarToken = 'StarToken',
EqualToken = 'EqualToken',
LessThanToken = 'LessThanToken',
GreaterThanToken = 'GreaterThanToken',
// Keywords
NamespaceKeyword = 'NamespaceKeyword',
IncludeKeyword = 'IncludeKeyword',
CppIncludeKeyword = 'CppIncludeKeyword',
ExceptionKeyword = 'ExceptionKeyword',
ServiceKeyword = 'ServiceKeyword',
ExtendsKeyword = 'ExtendsKeyword',
RequiredKeyword = 'RequiredKeyword',
OptionalKeyword = 'OptionalKeyword',
FalseKeyword = 'FalseKeyword',
TrueKeyword = 'TrueKeyword',
ConstKeyword = 'ConstKeyword',
DoubleKeyword = 'DoubleKeyword',
StructKeyword = 'StructKeyword',
TypedefKeyword = 'TypedefKeyword',
UnionKeyword = 'UnionKeyword',
StringKeyword = 'StringKeyword',
BinaryKeyword = 'BinaryKeyword',
BoolKeyword = 'BoolKeyword',
ByteKeyword = 'ByteKeyword',
EnumKeyword = 'EnumKeyword',
SenumKeyword = 'SenumKeyword',
ListKeyword = 'ListKeyword',
SetKeyword = 'SetKeyword',
MapKeyword = 'MapKeyword',
I8Keyword = 'I8Keyword',
I16Keyword = 'I16Keyword',
I32Keyword = 'I32Keyword',
I64Keyword = 'I64Keyword',
ThrowsKeyword = 'ThrowsKeyword',
VoidKeyword = 'VoidKeyword',
OnewayKeyword = 'OnewayKeyword',
// Other
Annotation = 'Annotation',
Annotations = 'Annotations',
EOF = 'EOF',
}

View File

@@ -0,0 +1,41 @@
/*
* 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 { SyntaxType, type ContainerType, type MapType, type FieldType } from './type';
export function convertIntToString(fType: FieldType): FieldType {
const fieldType = { ...fType };
const intTypes = [
SyntaxType.I8Keyword,
SyntaxType.I16Keyword,
SyntaxType.I32Keyword,
SyntaxType.I64Keyword,
];
if (intTypes.includes(fieldType.type)) {
fieldType.type = SyntaxType.StringKeyword;
} else if ((fieldType as ContainerType).valueType) {
(fieldType as ContainerType).valueType = convertIntToString(
(fieldType as ContainerType).valueType,
);
if ((fieldType as MapType).keyType) {
(fieldType as MapType).keyType = convertIntToString(
(fieldType as MapType).keyType,
);
}
}
return fieldType;
}