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,124 @@
/*
* 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.
*/
export const POSITIONS = [
'query',
'body',
'path',
'header',
'entire_body',
'raw_body',
'status_code',
];
export const SERIALIZERS = ['json', 'form', 'urlencoded'];
export const UPPERCASE_METHODS = [
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
'HEAD',
];
export const LOWERCASE_METHODS = [
'get',
'post',
'put',
'delete',
'patch',
'head',
];
export const SERVICE_EXTENSTION_CONFIG_KEYS = ['uri_prefix'];
export const FUNCTION_EXTENSTION_CONFIG_KEYS = [
'serializer',
'uri',
'method',
'group',
'custom',
'version',
...LOWERCASE_METHODS,
];
export const FIELD_EXTENSTION_CONFIG_KEYS = [
'position',
'key',
'web_type',
'value_type',
'tag',
...POSITIONS,
];
export type Position =
| 'query'
| 'body'
| 'path'
| 'header'
| 'entire_body'
| 'raw_body'
| 'status_code';
export type Serializer = 'json' | 'form' | 'urlencoded';
export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
export interface ServiceExtensionConfig {
uri_prefix?: string;
}
export interface FunctionExtensionConfig {
serializer?: Serializer;
uri?: string;
method?: Method;
group?: string;
custom?: string;
// NOTE: used in bytedance
version?: string;
get?: string;
post?: string;
put?: string;
delete?: string;
}
export interface FieldExtensionConfig {
position?: Position;
key?: string;
web_type?: string;
value_type?: string;
query?: string;
body?: string;
path?: string;
header?: string;
entire_body?: string;
raw_body?: string;
status_code?: string;
tag?: string;
}
export interface ExtensionConfig
extends ServiceExtensionConfig,
FunctionExtensionConfig,
FieldExtensionConfig {}
export type ExtensionConfigStringKey = Exclude<
keyof ExtensionConfig,
'serializer' | 'method' | 'position'
>;

View File

@@ -0,0 +1,203 @@
/*
* 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 {
POSITIONS,
SERIALIZERS,
UPPERCASE_METHODS,
LOWERCASE_METHODS,
SERVICE_EXTENSTION_CONFIG_KEYS,
FUNCTION_EXTENSTION_CONFIG_KEYS,
FIELD_EXTENSTION_CONFIG_KEYS,
type ExtensionConfig,
type ExtensionConfigStringKey,
type FieldExtensionConfig,
type FunctionExtensionConfig,
type ServiceExtensionConfig,
type Serializer,
type Method,
type Position,
} from './extension_type';
const goJsonTagRegExp = /^\s*json:(\\?[',"])(.*?)\1/;
function getTag(newtag = '', inputStr = '') {
const tags: string[] = [];
if (inputStr.includes('omitempty')) {
tags.push('omitempty');
}
if (inputStr.includes('required')) {
tags.push('required');
}
if (inputStr === 'int2str') {
tags.push('int2str');
}
if (tags.length === 0) {
return newtag;
}
return newtag + (newtag.length > 0 ? ',' : '') + tags.join(',');
}
// NOTE: agw is a similar specification in Bytedance,
// so we should cover the agw specification here.
// some old rules should also be covered here.
// eslint-disable-next-line complexity
export function extractExtensionConfig(
key: string,
value: string,
ignoreTag = false,
) {
const config: ExtensionConfig = {};
// an old rule: go.tag = "json:\"id,omitempty\""
if (!ignoreTag && /^go\.tag/.test(key)) {
const matches = value.match(goJsonTagRegExp);
if (matches) {
/* istanbul ignore next */
const tagValues = (matches[2] || '').split(',');
const tagKey = tagValues[0];
/* istanbul ignore else */
if (tagKey) {
if (tagKey === '-') {
config.tag = 'ignore';
} else if (/^[a-zA-Z0-9_-]+$/.test(tagKey)) {
config.key = tagKey;
}
}
const extraInfos = tagValues.slice(1).map(item => item.trim());
if (extraInfos.includes('string')) {
config.value_type = 'string';
}
const newTag = getTag(config.tag, matches[2]);
if (newTag) {
config.tag = newTag;
}
}
return config;
}
// the agw rules: agw.source = 'header' or agw.target = 'http_code'
if (key === 'source' || key === 'target') {
/* istanbul ignore else */
if (value === 'http_code') {
config.position = 'status_code';
} else if (POSITIONS.includes(value)) {
config.position = value as Position;
}
} else if (key === 'method') {
// the agw rule: agw.method = 'POST|GET'
const method = value.split('|')[0];
if (UPPERCASE_METHODS.includes(method)) {
config.method = method as Method;
}
} else if (key === 'position') {
if (POSITIONS.includes(value)) {
config.position = value as Position;
}
} else if (key === 'serializer') {
if (SERIALIZERS.includes(value)) {
config.serializer = value as Serializer;
}
} else if (LOWERCASE_METHODS.includes(key)) {
config.method = key.toUpperCase() as Method;
config.uri = value;
} else if (POSITIONS.includes(key)) {
config.position = key as Position;
// cover an old rule: (api.body = "tags, omitempty")
const parts = value.split(',');
config.key = parts[0].trim().replace(/\[\]$/, '');
const newTag = getTag(config.tag, value);
if (newTag) {
config.tag = newTag;
}
} else if (
[
'uri_prefix',
'uri',
'group',
'custom',
'version',
'key',
'web_type',
'value_type',
'tag',
].includes(key)
) {
config[key as 'uri_prefix'] = value;
} else if (key === 'req.headers') {
// NOTE: Compliance with old specifications
if (value.includes('x-www-form-urlencoded')) {
config.serializer = 'urlencoded';
} else if (value.includes('form-data')) {
config.serializer = 'form';
} else if (value.includes('json')) {
config.serializer = 'json';
}
} else if (key === 'js_conv' && ['string', 'str', 'true'].includes(value)) {
// NOTE: used in bytedance
const newTag = getTag(config.tag, 'int2str');
if (newTag) {
config.tag = newTag;
}
}
return config;
}
export function filterConfig(
config: ExtensionConfig,
keys: string[],
): ExtensionConfig {
const filteredConfig: ExtensionConfig = {};
Object.keys(config).forEach(key => {
if (keys.includes(key)) {
filteredConfig[key as ExtensionConfigStringKey] =
config[key as ExtensionConfigStringKey];
}
});
return filteredConfig;
}
export function filterFieldExtensionConfig(config: ExtensionConfig) {
return filterConfig(
config,
FIELD_EXTENSTION_CONFIG_KEYS,
) as FieldExtensionConfig;
}
export function filterFunctionExtensionConfig(config: ExtensionConfig) {
return filterConfig(
config,
FUNCTION_EXTENSTION_CONFIG_KEYS,
) as FunctionExtensionConfig;
}
export function filterServiceExtensionConfig(config: ExtensionConfig) {
return filterConfig(
config,
SERVICE_EXTENSTION_CONFIG_KEYS,
) as ServiceExtensionConfig;
}

View File

@@ -0,0 +1,17 @@
/*
* 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.
*/
export * from './unify';

View File

@@ -0,0 +1,199 @@
/*
* 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 * as path from 'path';
import * as fs from 'fs';
import { parse as protoParse } from 'proto-parser';
import { logAndThrowError } from '../utils';
import {
type ProtoDocument,
type ProtoError,
SyntaxType,
type MessageDefinition,
type ServiceDefinition,
type MethodDefinition,
type NamespaceBase,
type NamespaceDefinition,
type FieldExtensionConfig,
type ServiceExtensionConfig,
type FunctionExtensionConfig,
} from './type';
import * as extensionUtil from '../common/extension_util';
export * from './type';
// NOTE: cover old Rules: (api_method) = 'POST'
const oldRuleRegExp =
/\(api_req\)\.|\(api_resp\)\.|\(api_method\)\.|\(pb_idl\.api_method\)\./;
function extractExtensionConfigFromOption(
optionKey: string,
optionValue: string,
) {
let key = '';
if (/^\(api\.(.*)\)/.test(optionKey)) {
key = optionKey.replace(/^\(api\.(.*)\)/, '$1');
} else if (oldRuleRegExp.test(optionKey)) {
key = optionKey.replace(oldRuleRegExp, '');
} else {
return false;
}
const config = extensionUtil.extractExtensionConfig(key, optionValue);
return config;
}
function convertFieldOptions(message: MessageDefinition) {
const { name, fields } = message;
for (const field of Object.values(fields)) {
if (field.options && Object.keys(field.options).length > 0) {
const extensionConfig: FieldExtensionConfig = {};
const fieldName = field.name;
const fieldType = field.type;
for (const key of Object.keys(field.options)) {
const value = field.options[key];
const config = extractExtensionConfigFromOption(key, value);
/* istanbul ignore else */
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 (
['double', 'float', 'bool', 'bytes'].includes(fieldType.value)
) {
const errorMessage = `the type of path parameter '${fieldName}' in '${name}' should be string or integer`;
logAndThrowError(errorMessage);
}
}
Object.assign(extensionConfig, config);
}
}
field.extensionConfig =
extensionUtil.filterFieldExtensionConfig(extensionConfig);
}
}
}
function convertMethodOptions(method: MethodDefinition) {
if (!(method.options && Object.keys(method.options).length > 0)) {
return;
}
const extensionConfig: FunctionExtensionConfig = {};
for (const key of Object.keys(method.options)) {
const value = method.options[key];
const config = extractExtensionConfigFromOption(key, value);
/* istanbul ignore else */
if (config) {
Object.assign(extensionConfig, config);
}
}
method.extensionConfig =
extensionUtil.filterFunctionExtensionConfig(extensionConfig);
}
// NOTE: omit message definitions nested in a service definition
function convertServiceOptions(service: ServiceDefinition) {
if (service.options && Object.keys(service.options).length > 0) {
const extensionConfig: ServiceExtensionConfig = {};
for (const key of Object.keys(service.options)) {
const value = service.options[key];
const config = extractExtensionConfigFromOption(key, value);
/* istanbul ignore else */
if (config) {
Object.assign(extensionConfig, config);
}
}
service.extensionConfig =
extensionUtil.filterServiceExtensionConfig(extensionConfig);
}
if (Object.keys(service.methods).length > 0) {
Object.keys(service.methods).forEach(serviceName => {
convertMethodOptions(service.methods[serviceName]);
});
}
}
function convertNamespace(namespaceBase: NamespaceBase) {
const { nested } = namespaceBase;
/* istanbul ignore next */
if (!(nested && Object.keys(nested).length > 0)) {
return;
}
for (const name of Object.keys(nested)) {
const nestedStatement = nested[name];
const { syntaxType } = nestedStatement;
/* istanbul ignore else */
if (syntaxType === SyntaxType.ServiceDefinition) {
convertServiceOptions(nestedStatement as ServiceDefinition);
} else if (syntaxType === SyntaxType.MessageDefinition) {
convertFieldOptions(nestedStatement as MessageDefinition);
} else if (syntaxType === SyntaxType.NamespaceDefinition) {
convertNamespace(nestedStatement as NamespaceDefinition);
}
}
}
export function parse(source: string) {
let content: string;
let filePath = 'source';
if (/\.proto$/.test(source)) {
filePath = path.resolve(process.cwd(), source);
if (!fs.existsSync(filePath)) {
const message = `no such file: ${filePath}`;
logAndThrowError(message);
}
content = fs.readFileSync(filePath, 'utf8');
} else {
content = source;
}
const document: ProtoDocument | ProtoError = protoParse(content);
if ((document as ProtoError).syntaxType === SyntaxType.ProtoError) {
const { line, message } = document as ProtoError;
const fullMessage = `${message}(${filePath}:${line}:0)`;
logAndThrowError(fullMessage);
}
convertNamespace((document as ProtoDocument).root);
return document as ProtoDocument;
}

View File

@@ -0,0 +1,154 @@
/*
* 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 type KeywordType =
| 'double'
| 'float'
| 'int32'
| 'int64'
| 'uint32'
| 'uint64'
| 'sint32'
| 'sint64'
| 'fixed32'
| 'fixed64'
| 'sfixed32'
| 'sfixed64'
| 'bool'
| 'string'
| 'bytes';
export type FieldRule = 'repeated' | 'required';
export interface BaseType {
syntaxType: SyntaxType.BaseType;
value: KeywordType;
}
export interface Identifier {
syntaxType: SyntaxType.Identifier;
value: string;
resolvedValue?: string;
}
export type FieldType = BaseType | Identifier;
export interface ReflectionObject {
name: string;
fullName?: string;
options?: Record<string, string>;
comment?: string;
}
export interface FieldDefinition extends ReflectionObject {
syntaxType: SyntaxType.FieldDefinition;
id: number;
type: FieldType;
rule?: FieldRule;
optional: boolean;
required: boolean;
repeated: boolean;
map: boolean;
extend?: string;
keyType?: FieldType;
extensionConfig?: FieldExtensionConfig;
}
export interface OneofDefinition extends ReflectionObject {
syntaxType: SyntaxType.OneofDefinition;
oneof: string[];
}
export interface MethodDefinition extends ReflectionObject {
syntaxType: SyntaxType.MethodDefinition;
requestType: FieldType;
responseType: FieldType;
extensionConfig?: FunctionExtensionConfig;
}
export interface NamespaceBase extends ReflectionObject {
syntaxType: SyntaxType;
nested?: Record<string, NamespaceBase>;
}
export interface NamespaceDefinition extends NamespaceBase {
syntaxType: SyntaxType.NamespaceDefinition;
}
export interface MessageDefinition extends NamespaceBase {
syntaxType: SyntaxType.MessageDefinition;
fields: Record<string, FieldDefinition>;
oneofs: Record<string, OneofDefinition>;
extensions?: string[];
reserved?: number[] | string;
}
export interface EnumDefinition extends NamespaceBase {
syntaxType: SyntaxType.EnumDefinition;
values: Record<string, number>;
reserved?: number[] | string;
}
export interface ServiceDefinition extends NamespaceBase {
syntaxType: SyntaxType.ServiceDefinition;
methods: Record<string, MethodDefinition>;
extensionConfig?: ServiceExtensionConfig;
}
export interface ProtoRoot extends NamespaceBase {
syntaxType: SyntaxType.ProtoRoot;
}
export interface ProtoDocument {
syntaxType: SyntaxType.ProtoDocument;
imports?: string[];
weakImports?: string[];
package?: string;
syntax: 'proto2' | 'proto3';
root: ProtoRoot;
}
export interface ProtoError {
syntaxType: SyntaxType.ProtoError;
line: number;
message: string;
error: Error;
}
export enum SyntaxType {
BaseType = 'BaseType',
Identifier = 'Identifier',
OneofDefinition = 'OneofDefinition',
FieldDefinition = 'FieldDefinition',
MethodDefinition = 'MethodDefinition',
NamespaceDefinition = 'NamespaceDefinition',
MessageDefinition = 'MessageDefinition',
EnumDefinition = 'EnumDefinition',
ServiceDefinition = 'ServiceDefinition',
ProtoRoot = 'ProtoRoot',
ProtoDocument = 'ProtoDocument',
ProtoError = 'ProtoError',
}

View File

@@ -0,0 +1,288 @@
/*
* 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 * as path from 'path';
import * as fs from 'fs';
import { parse as thriftParse } from '@lancewuz/thrift-parser';
import { logAndThrowError } from '../utils';
import {
type ThriftDocument,
type ThriftErrors,
SyntaxType,
type InterfaceWithFields,
type Annotation,
type ServiceDefinition,
type FunctionDefinition,
type EnumDefinition,
type FieldExtensionConfig,
type ServiceExtensionConfig,
type FunctionExtensionConfig,
type Comment,
} from './type';
import * as extensionUtil from '../common/extension_util';
// export statements
export * from './type';
// global variables
let reviseTailComment = true;
function extractExtensionConfigFromAnnotation(annotation: Annotation) {
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);
return config;
}
function convertFieldAnnotations(struct: InterfaceWithFields) {
const name = struct.name.value;
const { fields } = struct;
for (const field of fields) {
if (field.annotations && field.annotations.annotations.length > 0) {
const extensionConfig: FieldExtensionConfig = {};
const fieldName = field.name.value;
const fieldSyntaxType = field.fieldType.type;
for (const annotation of field.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(fieldSyntaxType)
) {
const message = `the type of path parameter '${fieldName}' in '${name}' should be string or integer`;
logAndThrowError(message);
}
}
Object.assign(extensionConfig, config);
}
}
field.extensionConfig =
extensionUtil.filterFieldExtensionConfig(extensionConfig);
}
}
}
function convertFunctionAnnotations(func: FunctionDefinition) {
const { annotations } = func;
if (!(annotations && annotations.annotations.length > 0)) {
return;
}
const extensionConfig: FunctionExtensionConfig = {};
for (const annotation of annotations.annotations) {
const config = extractExtensionConfigFromAnnotation(annotation);
/* istanbul ignore next */
if (config) {
Object.assign(extensionConfig, config);
}
}
func.extensionConfig =
extensionUtil.filterFunctionExtensionConfig(extensionConfig);
}
function convertServiceAnnotations(service: ServiceDefinition) {
const { annotations } = service;
if (annotations && annotations.annotations.length > 0) {
const extensionConfig: ServiceExtensionConfig = {};
for (const annotation of annotations.annotations) {
const config = extractExtensionConfigFromAnnotation(annotation);
/* istanbul ignore else */
if (config) {
Object.assign(extensionConfig, config);
}
}
service.extensionConfig =
extensionUtil.filterServiceExtensionConfig(extensionConfig);
}
if (service.functions.length > 0) {
service.functions.forEach(func => {
convertFunctionAnnotations(func);
});
}
}
function reviseFieldComments(struct: InterfaceWithFields) {
const { fields } = struct;
/* istanbul ignore next */
if (fields.length < 2) {return;}
for (let i = fields.length - 1; i > 0; i--) {
const currentField = fields[i];
const prevField = fields[i - 1];
const prevFieldEndLine = prevField.loc.end.line;
const prevFieldComments = prevField.comments;
for (let j = 0; j < prevFieldComments.length; j++) {
if (prevFieldComments[j].loc.end.line > prevFieldEndLine) {
const dislocatedComments = prevFieldComments.splice(
j,
prevFieldComments.length - j,
);
currentField.comments = [
...dislocatedComments,
...currentField.comments,
];
break;
}
}
}
}
function reviseEnumMemberComments(enm: EnumDefinition) {
const { members } = enm;
/* istanbul ignore next */
if (members.length < 2) {return;}
for (let i = 0; i < members.length - 1; i++) {
const currentMember = members[i];
const nextMember = members[i + 1];
const currentMemberEndLine = currentMember.loc.end.line;
const nextMemberFirstComment = nextMember.comments[0];
if (
nextMemberFirstComment &&
nextMemberFirstComment.loc.end.line === currentMemberEndLine
) {
const dislocatedComment = nextMember.comments.shift() as Comment;
currentMember.comments.push(dislocatedComment);
}
}
}
function reviseFunctionComments(service: ServiceDefinition) {
const { functions } = service;
if (functions.length < 2) {return;}
for (let i = 0; i < functions.length - 1; i++) {
const currentFunction = functions[i];
const nextFunction = functions[i + 1];
const currentFunctionEndLine = currentFunction.loc.end.line;
const nextFunctionFirstComment = nextFunction.comments[0];
if (
nextFunctionFirstComment &&
nextFunctionFirstComment.loc.end.line === currentFunctionEndLine
) {
const dislocatedComment = nextFunction.comments.shift() as Comment;
currentFunction.comments.push(dislocatedComment);
}
}
}
export interface ParseOption {
reviseTailComment?: boolean;
}
const defualtParseOption = {
reviseTailComment: true,
};
export function parse(source: string, option?: ParseOption): ThriftDocument {
let content: string;
let filePath = 'source';
if (/\.thrift$/.test(source)) {
filePath = path.resolve(process.cwd(), source);
if (!fs.existsSync(filePath)) {
const message = `no such file: ${filePath}`;
logAndThrowError(message);
}
content = fs.readFileSync(filePath, 'utf8');
} else {
content = source;
}
const document: ThriftDocument | ThriftErrors = thriftParse(content);
if ((document as ThriftErrors).type === SyntaxType.ThriftErrors) {
const error = (document as ThriftErrors).errors[0];
const { start } = error.loc;
const message = `${error.message}(${filePath}:${start.line}:${start.column})`;
logAndThrowError(message);
}
const parseOption = { ...defualtParseOption, ...option };
reviseTailComment = parseOption.reviseTailComment;
for (const statement of (document as ThriftDocument).body) {
/* istanbul ignore else */
if (
[SyntaxType.StructDefinition, SyntaxType.UnionDefinition].includes(
statement.type,
)
) {
convertFieldAnnotations(statement as InterfaceWithFields);
if (reviseTailComment) {
reviseFieldComments(statement as InterfaceWithFields);
}
} else if (statement.type === SyntaxType.ServiceDefinition) {
convertServiceAnnotations(statement);
if (reviseTailComment) {
reviseFunctionComments(statement);
}
} else if (statement.type === SyntaxType.EnumDefinition) {
/* istanbul ignore else */
if (reviseTailComment) {
reviseEnumMemberComments(statement);
}
}
}
return document as ThriftDocument;
}

View File

@@ -0,0 +1,460 @@
/*
* 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 ThriftError {
type: ErrorType;
message: string;
loc: TextLocation;
}
export interface ParseError extends ThriftError {
type: ErrorType.ParseError;
}
export interface ScanError extends ThriftError {
type: ErrorType.ScanError;
}
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 ThriftDocument extends Node {
type: SyntaxType.ThriftDocument;
body: Array<ThriftStatement>;
tokens?: Array<Token>;
}
export interface ThriftErrors {
type: SyntaxType.ThriftErrors;
errors: Array<ThriftError>;
}
export type ThriftStatement =
| NamespaceDefinition
| IncludeDefinition
| CppIncludeDefinition
| ConstDefinition
| StructDefinition
| EnumDefinition
| ExceptionDefinition
| UnionDefinition
| TypedefDefinition
| 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 NamespaceDefinition extends PrimarySyntax {
type: SyntaxType.NamespaceDefinition;
scope: Identifier;
name: Identifier;
}
export interface ConstDefinition extends PrimarySyntax {
type: SyntaxType.ConstDefinition;
name: Identifier;
fieldType: FieldType;
initializer: ConstValue;
annotations?: Annotations;
}
export type FieldRequired = 'required' | 'optional';
export interface IncludeDefinition extends PrimarySyntax {
type: SyntaxType.IncludeDefinition;
path: StringLiteral;
}
export interface CppIncludeDefinition extends PrimarySyntax {
type: SyntaxType.CppIncludeDefinition;
path: StringLiteral;
}
export interface InterfaceWithFields extends PrimarySyntax {
name: Identifier;
fields: Array<FieldDefinition>;
annotations?: Annotations;
}
export interface StructDefinition extends InterfaceWithFields {
type: SyntaxType.StructDefinition;
}
export interface UnionDefinition extends InterfaceWithFields {
type: SyntaxType.UnionDefinition;
}
export interface ExceptionDefinition extends InterfaceWithFields {
type: SyntaxType.ExceptionDefinition;
}
export interface FieldDefinition extends PrimarySyntax {
type: SyntaxType.FieldDefinition;
name: Identifier;
fieldID: FieldID | null;
fieldType: FunctionType;
requiredness: FieldRequired | null;
defaultValue: ConstValue | null;
annotations?: Annotations;
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;
}
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;
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;
extensionConfig?: FunctionExtensionConfig;
}
export interface ParametersDefinition extends SyntaxNode {
type: SyntaxType.ParametersDefinition;
fields: Array<FieldDefinition>;
}
export interface ThrowsDefinition extends SyntaxNode {
type: SyntaxType.ThrowsDefinition;
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;
annotations?: Annotations;
}
export enum ErrorType {
ParseError = 'ParseError',
ScanError = 'ScanError',
}
export enum SyntaxType {
ThriftDocument = 'ThriftDocument',
ThriftErrors = 'ThriftErrors',
Identifier = 'Identifier',
FieldID = 'FieldID',
// Statements
NamespaceDefinition = 'NamespaceDefinition',
IncludeDefinition = 'IncludeDefinition',
CppIncludeDefinition = 'CppIncludeDefinition',
ConstDefinition = 'ConstDefinition',
StructDefinition = 'StructDefinition',
EnumDefinition = 'EnumDefinition',
ServiceDefinition = 'ServiceDefinition',
ExceptionDefinition = 'ExceptionDefinition',
TypedefDefinition = 'TypedefDefinition',
UnionDefinition = 'UnionDefinition',
// Fields
FieldDefinition = 'FieldDefinition',
FunctionDefinition = 'FunctionDefinition',
ParametersDefinition = 'ParametersDefinition',
ThrowsDefinition = 'ThrowsDefinition',
// 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,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;
}

View File

@@ -0,0 +1,45 @@
/*
* 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 { logger } from '@coze-arch/rush-logger';
export const logAndThrowError = (logMessage: string, errorMessage?: string) => {
logger.error(logMessage);
const message = errorMessage || logMessage;
throw new Error(message);
};
export function mergeObject(
target: { [key: string]: any },
...sources: { [key: string]: any }[]
): { [key: string]: any } {
const newObj = { ...target };
if (!sources) {return newObj;}
for (const source of sources) {
for (const key of Object.keys(source)) {
if (typeof source[key] !== 'undefined') {
newObj[key] = source[key];
}
}
}
return newObj;
}
export function getPosixPath(filePath: string) {
return filePath.replace(/\\/g, '/');
}