UNPKG

thrift-fmt-ts

Version:

A formatter of Thrift, re-implement of python thrift-fmt

924 lines (796 loc) 28.9 kB
import { CommonToken } from 'antlr4ts'; import { ParseTree, TerminalNode } from 'antlr4ts/tree'; import { ThriftData, ThriftParser, CommentChannel } from 'thrift-parser-ts'; import * as ThriftParserNS from 'thrift-parser-ts/lib/ThriftParser'; type IsKindFunc = (node: ParseTree) => boolean; type TightFN = (index: number, node: ParseTree) => boolean; type NodeProcessFunc = (this: PureThriftFormatter, node: ParseTree) => void; type FieldContext = ThriftParserNS.FieldContext | ThriftParserNS.Enum_fieldContext; export interface Option { indent: number, patchRequired: boolean, patchSeparator: boolean, keepComment: boolean, alignByAssign: boolean, alignByField: boolean, } export const newOption = (opt?: Partial<Option>):Option => { const defaultOption: Option = { indent: 4, patchRequired: true, patchSeparator: true, keepComment: true, alignByAssign: false, alignByField: false, } return { ...defaultOption, ...opt, } } export const isToken = (node: ParseTree | undefined, text: string): boolean => { return node instanceof TerminalNode && node.symbol.text === text; } export const isEOF = (node: ParseTree): boolean => { return node instanceof TerminalNode && node.symbol.type === ThriftParser.EOF; } const fakeNodeLineNo = -1; const isFakeNode = (node: TerminalNode): boolean => { return node.symbol.line === fakeNodeLineNo; } const notSameClass = (a:ParseTree, b: ParseTree): boolean => { return a.constructor.name !== b.constructor.name; } const isNeedNewLineNode = (node: ParseTree): boolean => { return ( node instanceof ThriftParserNS.Enum_ruleContext || node instanceof ThriftParserNS.Struct_Context || node instanceof ThriftParserNS.Union_Context || node instanceof ThriftParserNS.Exception_Context || node instanceof ThriftParserNS.ServiceContext ); } const isFunctionOrThrowsListNode = (node: ParseTree): boolean => { return ( node instanceof ThriftParserNS.Function_Context || node instanceof ThriftParserNS.Throws_listContext ); } const isFieldOrEnumField = (node: ParseTree|undefined): boolean => { return ( node instanceof ThriftParserNS.FieldContext || node instanceof ThriftParserNS.Enum_fieldContext ); } const splitFieldChildrenByAssign = (node: FieldContext):[ParseTree[], ParseTree[]] => { const children: ParseTree[] = node.children || []; let i = 0; let curLeft = true; for (;i < node.childCount; i++) { const child = node.getChild(i); if (isToken(child, '=') || (child instanceof ThriftParserNS.List_separatorContext)){ curLeft = false; break } } // current child is belong to left. if (curLeft) { i++; } const left = children.slice(0, i); const right = children.slice(i); return [left, right]; } export const splitFieldByAssign = (node: FieldContext):[ParseTree, ParseTree] => { /* split field's children to [left, right] field: '1: required i32 number_a = 0,' left: '1: required i32 number_a' right: '= 0,' */ let left :FieldContext|undefined = undefined; let right :FieldContext|undefined = undefined; if (node instanceof ThriftParserNS.FieldContext) { left = new ThriftParserNS.FieldContext(node.parent, 0); right = new ThriftParserNS.FieldContext(node.parent, 0); } else { left = new ThriftParserNS.Enum_fieldContext(node.parent, 0); right = new ThriftParserNS.Enum_fieldContext(node.parent, 0); } const [leftChildren, rightChildren] = splitFieldChildrenByAssign(node); for (const child of leftChildren) { left.addAnyChild(child); } for (const child of rightChildren) { right.addAnyChild(child); } return [left, right]; } const getSplitFieldsLeftRightSize = (fields: ParseTree[]):[number, number] => { let leftMaxSize = 0; let rightMaxSize = 0; for (const field of fields) { const node = <FieldContext> field; const [left, right] = splitFieldByAssign(node); const leftSize = new PureThriftFormatter().formatNode(left).length; const rightSize = new PureThriftFormatter().formatNode(right).length; leftMaxSize = leftMaxSize > leftSize?leftMaxSize:leftSize; rightMaxSize = rightMaxSize > rightSize?rightMaxSize:rightSize; } return [leftMaxSize, rightMaxSize]; } export const getNodeChildren = (node: ParseTree): ParseTree[] => { const children = []; for (let i = 0; i < node.childCount; i++) { children.push(node.getChild(i)); } return children; } export const walkNode = (root: ParseTree, callback: (node: ParseTree) => void) => { const stack: ParseTree[] = [root]; while (stack.length > 0) { const node = stack.shift(); if (node === undefined) { break; } callback(node); const children = getNodeChildren(node); children.forEach(value => stack.push(value)) } } export const splitRepeatNodes = (nodes: ParseTree[], kindFn: IsKindFunc): [ParseTree[], ParseTree[]] => { const children = []; for (const [index, node] of nodes.entries()) { if (!kindFn(node)) { return [children, nodes.slice(index)]; } children.push(node); } return [children, []]; } const genInlineContext = (join = ' ', tightFn?: TightFN | undefined): NodeProcessFunc => { return function (this: PureThriftFormatter, node: ParseTree) { for (let i = 0; i < node.childCount; i++) { const child = node.getChild(i); if (i > 0 && join.length > 0) { if (!tightFn || !tightFn(i, child)) { this.append(join); } } this.processNode(child); } }; } const genSubblocksContext = (start: number, kindFn: IsKindFunc): NodeProcessFunc => { return function (this: PureThriftFormatter, node: ParseTree) { const children = getNodeChildren(node); this.processInlineNodes(children.slice(0, start)); this.newline(); const leftChildren = children.slice(start); const [subblocks, leftNodes] = splitRepeatNodes(leftChildren, kindFn); this.beforeSubblocks(subblocks); this.processBlockNodes(subblocks, ' '.repeat(this._option.indent)); this.afterSubblocks(subblocks); this.newline(); this.processInlineNodes(leftNodes); }; } const tupleTightInline = genInlineContext( ' ', (i, n) => isToken(n, '(') || isToken(n, ')') || isToken(n.parent?.getChild(i - 1), '(') || n instanceof ThriftParserNS.List_separatorContext ); const listSeparatorInline = genInlineContext( ' ', (_, node) => node instanceof ThriftParserNS.List_separatorContext ); const fieldSubblocks = genSubblocksContext( 3, (n) => n instanceof ThriftParserNS.FieldContext ); export class PureThriftFormatter { protected _option: Option = newOption(); protected currentIndent = ''; protected newlineCounter = 0; private _out = ''; formatNode(node: ParseTree): string { this._out = ''; this.newlineCounter = 0; this.currentIndent = ''; this.processNode(node); return this._out; } option(opt: Option) { this._option = opt; } get out(): string { return this._out; } private push(text: string) { this._out += text; } // if this.newlineCounter was set. `append` will first write newlines and then append protected append(text: string) { if (this.newlineCounter > 0) { this.push('\n'.repeat(this.newlineCounter)); this.newlineCounter = 0; } this.push(text); } // appendCurrentLine append to current line, and ignore this.newlineCounter. protected appendCurrentLine(text: string) { this.push(text); } protected newline(repeat = 1) { const diff = repeat - this.newlineCounter; if (diff > 0) { this.newlineCounter += diff; } } protected setCurrentIndent(indent = '') { this.currentIndent = indent; } protected pushCurrentIndent() { if (this.currentIndent.length > 0) { this.append(this.currentIndent); } } protected beforeBlockNode(_: ParseTree) {} // eslint-disable-line protected afterBlockNode(_: ParseTree) {} // eslint-disable-line protected beforeSubblocks(_: ParseTree[]) {} // eslint-disable-line protected afterSubblocks(_: ParseTree[]) {} // eslint-disable-line protected beforeProcessNode(_: ParseTree) {} // eslint-disable-line protected afterProcessNode(_: ParseTree) {} // eslint-disable-line protected processBlockNodes(nodes: ParseTree[], indent = '') { let lastNode: ParseTree | undefined = undefined; // eslint-disable-next-line for (let [index, node] of nodes.entries()) { if ( node instanceof ThriftParserNS.HeaderContext || node instanceof ThriftParserNS.DefinitionContext ) { node = node.getChild(0); } this.beforeBlockNode(node) if (index > 0 && lastNode !== undefined) { if (notSameClass(lastNode, node)|| isNeedNewLineNode(node)) { this.newline(2); } else { this.newline(); } } this.setCurrentIndent(indent); this.processNode(node); this.afterBlockNode(node); lastNode = node; } } protected processInlineNodes(nodes: ParseTree[], join = ' ') { // eslint-disable-next-line for (const [index, node] of nodes.entries()) { if (index > 0) { this.append(join); } this.processNode(node); } } protected processNode(node: ParseTree): void { this.beforeProcessNode(node) this._processNode(node) this.afterProcessNode(node) } private _processNode(node: ParseTree): void { if (node instanceof TerminalNode) { this.TerminalNode(node); } else if (node instanceof ThriftParserNS.DocumentContext) { this.DocumentContext(node); } else if (node instanceof ThriftParserNS.HeaderContext) { this.HeaderContext(node); } else if (node instanceof ThriftParserNS.DefinitionContext) { this.DefinitionContext(node); } else if (node instanceof ThriftParserNS.Include_Context) { this.Include_Context(node); } else if (node instanceof ThriftParserNS.Namespace_Context) { this.Namespace_Context(node); } else if (node instanceof ThriftParserNS.Typedef_Context) { this.Typedef_Context(node); } else if (node instanceof ThriftParserNS.Base_typeContext) { this.Base_typeContext(node); } else if (node instanceof ThriftParserNS.Real_base_typeContext) { this.Real_base_typeContext(node); } else if (node instanceof ThriftParserNS.Const_ruleContext) { this.Const_ruleContext(node); } else if (node instanceof ThriftParserNS.Const_valueContext) { this.Const_valueContext(node); } else if (node instanceof ThriftParserNS.IntegerContext) { this.IntegerContext(node); } else if (node instanceof ThriftParserNS.Container_typeContext) { this.Container_typeContext(node); } else if (node instanceof ThriftParserNS.Set_typeContext) { this.Set_typeContext(node); } else if (node instanceof ThriftParserNS.List_typeContext) { this.List_typeContext(node); } else if (node instanceof ThriftParserNS.Cpp_typeContext) { this.Cpp_typeContext(node); } else if (node instanceof ThriftParserNS.Const_mapContext) { this.Const_mapContext(node); } else if (node instanceof ThriftParserNS.Const_map_entryContext) { this.Const_map_entryContext(node); } else if (node instanceof ThriftParserNS.List_separatorContext) { this.List_separatorContext(node); } else if (node instanceof ThriftParserNS.Field_idContext) { this.Field_idContext(node); } else if (node instanceof ThriftParserNS.Field_reqContext) { this.Field_reqContext(node); } else if (node instanceof ThriftParserNS.Field_typeContext) { this.Field_typeContext(node); } else if (node instanceof ThriftParserNS.Map_typeContext) { this.Map_typeContext(node); } else if (node instanceof ThriftParserNS.Const_listContext) { this.Const_listContext(node); } else if (node instanceof ThriftParserNS.Enum_ruleContext) { this.Enum_ruleContext(node); } else if (node instanceof ThriftParserNS.Struct_Context) { this.Struct_Context(node); } else if (node instanceof ThriftParserNS.Union_Context) { this.Union_Context(node); } else if (node instanceof ThriftParserNS.Exception_Context) { this.Exception_Context(node); } else if (node instanceof ThriftParserNS.Enum_fieldContext) { this.Enum_fieldContext(node); } else if (node instanceof ThriftParserNS.FieldContext) { this.FieldContext(node); } else if (node instanceof ThriftParserNS.Function_Context) { this.Function_Context(node); } else if (node instanceof ThriftParserNS.OnewayContext) { this.OnewayContext(node); } else if (node instanceof ThriftParserNS.Function_typeContext) { this.Function_typeContext(node); } else if (node instanceof ThriftParserNS.Throws_listContext) { this.Throws_listContext(node); } else if (node instanceof ThriftParserNS.Type_annotationsContext) { this.Type_annotationsContext(node); } else if (node instanceof ThriftParserNS.Type_annotationContext) { this.Type_annotationContext(node); } else if (node instanceof ThriftParserNS.Annotation_valueContext) { this.Annotation_valueContext(node); } else if (node instanceof ThriftParserNS.ServiceContext) { this.ServiceContext(node); } else if (node instanceof ThriftParserNS.SenumContext) { this.SenumContext(node); } else { // unsupport node } } protected TerminalNode(node: TerminalNode) { if (isEOF(node)) { return; } this.pushCurrentIndent(); this.setCurrentIndent(''); this.append(node.symbol.text || ''); } protected DocumentContext: NodeProcessFunc = function (this: PureThriftFormatter, node: ParseTree) { const children = getNodeChildren(node); this.processBlockNodes(children); }; protected HeaderContext: NodeProcessFunc = function (this: PureThriftFormatter,node: ParseTree) { this.processNode(node.getChild(0)); }; protected DefinitionContext: NodeProcessFunc = function (this: PureThriftFormatter, node: ParseTree) { this.processNode(node.getChild(0)); }; // TODO: clean this? protected Include_Context: NodeProcessFunc = genInlineContext(); protected Namespace_Context: NodeProcessFunc = genInlineContext(); protected Typedef_Context: NodeProcessFunc = genInlineContext(); protected Base_typeContext: NodeProcessFunc = genInlineContext(); protected Field_typeContext: NodeProcessFunc = genInlineContext(); protected Real_base_typeContext: NodeProcessFunc = genInlineContext(); protected Const_ruleContext: NodeProcessFunc = genInlineContext(); protected Const_valueContext: NodeProcessFunc = genInlineContext(); protected IntegerContext: NodeProcessFunc = genInlineContext(); protected Container_typeContext: NodeProcessFunc = genInlineContext(''); protected Set_typeContext: NodeProcessFunc = genInlineContext(''); protected List_typeContext: NodeProcessFunc = genInlineContext(''); protected Cpp_typeContext: NodeProcessFunc = genInlineContext(); protected Const_mapContext: NodeProcessFunc = genInlineContext(); protected Const_map_entryContext: NodeProcessFunc = genInlineContext(); protected List_separatorContext: NodeProcessFunc = genInlineContext(); protected Field_idContext: NodeProcessFunc = genInlineContext(''); protected Field_reqContext: NodeProcessFunc = genInlineContext(); protected Map_typeContext: NodeProcessFunc = genInlineContext( ' ', (i, n) => !isToken(n.parent?.getChild(i - 1), ',') ); protected Const_listContext: NodeProcessFunc = listSeparatorInline protected Enum_ruleContext: NodeProcessFunc = genSubblocksContext( 3, (n) => n instanceof ThriftParserNS.Enum_fieldContext ); protected Enum_fieldContext: NodeProcessFunc = listSeparatorInline protected Struct_Context: NodeProcessFunc = fieldSubblocks protected Union_Context: NodeProcessFunc = fieldSubblocks protected Exception_Context: NodeProcessFunc = fieldSubblocks protected FieldContext: NodeProcessFunc = listSeparatorInline protected Function_Context: NodeProcessFunc = tupleTightInline protected OnewayContext: NodeProcessFunc = genInlineContext(); protected Function_typeContext: NodeProcessFunc = genInlineContext(); protected Throws_listContext: NodeProcessFunc = tupleTightInline protected Type_annotationsContext: NodeProcessFunc = tupleTightInline protected Type_annotationContext: NodeProcessFunc = tupleTightInline protected Annotation_valueContext: NodeProcessFunc = genInlineContext(); protected ServiceContext_Default: NodeProcessFunc = genSubblocksContext( 3, (n:ParseTree) => n instanceof ThriftParserNS.Function_Context ); protected ServiceContext_Extends: NodeProcessFunc = genSubblocksContext( 5, (n:ParseTree) => n instanceof ThriftParserNS.Function_Context ); protected ServiceContext: NodeProcessFunc = function (this: PureThriftFormatter, n: ParseTree) { const node = <ThriftParserNS.ServiceContext>n; if (isToken(node.getChild(2), 'extends')) { this.ServiceContext_Extends(node); } else { this.ServiceContext_Default(node); } }; protected SenumContext: NodeProcessFunc = function (this: PureThriftFormatter, _:ParseTree) {}; // eslint-disable-line } const patchFieldRequired = (n: ParseTree): void => { if (!(n instanceof ThriftParserNS.FieldContext)) { return; } if (n.parent === undefined || isFunctionOrThrowsListNode(n.parent)) { return; } let i = 0; for (; i < n.childCount; i++) { const child = n.getChild(i); if (child instanceof ThriftParserNS.Field_reqContext) { return; } if (child instanceof ThriftParserNS.Field_typeContext) { break; } } const fakeToken = new CommonToken(ThriftParser.T__20, 'required'); fakeToken.line = fakeNodeLineNo; fakeToken.charPositionInLine = fakeNodeLineNo; fakeToken.tokenIndex = -1; const fakeNode = new TerminalNode(fakeToken); const fakeReq = new ThriftParserNS.Field_reqContext(n, 0); fakeNode.setParent(fakeReq); fakeReq.addChild(fakeNode); fakeReq.setParent(n); n.children?.splice(i, 0, fakeReq); // addChild } const patchFieldListSeparator = (n: ParseTree): void => { if (!(n instanceof ThriftParserNS.Enum_fieldContext || n instanceof ThriftParserNS.FieldContext || n instanceof ThriftParserNS.Function_Context)) { return; } const child = n.getChild(n.childCount - 1); if (child instanceof ThriftParserNS.List_separatorContext) { const comma = <TerminalNode>child.getChild(0); const token = <CommonToken> comma.symbol; token.text = ','; return; } const fakeToken = new CommonToken(ThriftParser.COMMA, ','); fakeToken.line = fakeNodeLineNo; fakeToken.charPositionInLine = fakeNodeLineNo; fakeToken.tokenIndex = -1; const fakeNode = new TerminalNode(fakeToken); const fakeCtx = new ThriftParserNS.List_separatorContext(n, 0); fakeNode.setParent(fakeCtx); fakeCtx.addChild(fakeNode); fakeCtx.setParent(n); n.addChild(fakeCtx); } const patchRemoveLastListSeparator = (n: ParseTree): void => { const isInlineField = n instanceof ThriftParserNS.FieldContext && n.parent !== undefined && isFunctionOrThrowsListNode(n.parent); const isInlineNode = n instanceof ThriftParserNS.Type_annotationContext; if (!(isInlineField || isInlineNode)) { return; } if (n.parent === undefined) { return; } let last = false; const brothers = n.parent.children || []; const bortherCount = n.parent.childCount; for (let i = 0; i < bortherCount; i++) { if (brothers[i] === n) { if (i === bortherCount - 1 || notSameClass(n, brothers[i + 1])) { last = true; break; } } } if (last) { const child = n.getChild(n.childCount - 1); if (child instanceof ThriftParserNS.List_separatorContext) { n.removeLastChild(); } } } const calcSubBlocksCommentPadding = (subblocks: ParseTree[]): number => { let padding = 0; for (const subblock of subblocks) { const nodeLength = (new PureThriftFormatter().formatNode(subblock)).length; padding = padding >= nodeLength? padding: nodeLength; } if (padding > 0) { padding = padding + 1; } return padding; } export const calcFieldAlignByAssignPadding = (fields: ParseTree[]): [number, number] => { if (fields.length === 0 || !isFieldOrEnumField(fields[0]) ) { return [0, 0]; } const [leftMaxSize, rightMaxSize] = getSplitFieldsLeftRightSize(fields); // add extra space "xxx = yyy" -> "xxx" + " " + "= yyy" const assignPadding = leftMaxSize + 1; let commentPadding = assignPadding + rightMaxSize + 1; // add an extra space for next comment /* if it is not list sep, need add extra space case 1 --> "1: bool a = true," ---> "1: bool a" + " " + "= true," case 2 --> "2: bool b," ---> "2: bool b" + "" + "," */ if (rightMaxSize <= 1) { // case 1 commentPadding = commentPadding - 1; } return [assignPadding, commentPadding]; } const getFieldChildName = (n: ParseTree): string => { if (isToken(n, '=')) { return '='; } return n.constructor.name; } const calcFieldAlignByFieldPaddingMap = (fields: ParseTree[]):[Map<string, number>, number] => { const paddingMap: Map<string, number> = new Map(); if (fields.length == 0 || !isFieldOrEnumField(fields[0])) { return [paddingMap, 0]; } const nameLevels: Map<string, number> = new Map(); for (const field of fields) { let i = 0; for (;i < field.childCount -1; i++) { const nameA = getFieldChildName(field.getChild(i)) const nameB = getFieldChildName(field.getChild(i+1)) if (!nameLevels.has(nameA)) { nameLevels.set(nameA, 0); } if (!nameLevels.has(nameB)) { nameLevels.set(nameB, 0); } const levelB = Math.max(nameLevels.get(nameB)!, nameLevels.get(nameA)! +1); // eslint-disable-line nameLevels.set(nameB, levelB); } } // 检查 levles 连续 if (Math.max(...nameLevels.values()) != (nameLevels.size -1)) { return [paddingMap, 0]; } const levelLength: Map<number, number> = new Map(); for (const field of fields) { let i = 0; for (;i < field.childCount; i++) { const child = field.getChild(i); const level = nameLevels.get(getFieldChildName(child))!; // eslint-disable-line const length = new PureThriftFormatter().formatNode(child).length; levelLength.set(level, Math.max(levelLength.get(level) || 0, length)); } } const sep = new ThriftParserNS.List_separatorContext(undefined, 0); const levelPadding: Map<number, number> = new Map(); for (const [level, ] of levelLength) { let padding = level; if (level === nameLevels.get(getFieldChildName(sep))) { padding -= 1; } let i = 0; for (;i < level; i++) { padding += levelLength.get(i) || 0; } levelPadding.set(level, padding); } for (const [name, level] of nameLevels) { paddingMap.set(name, levelPadding.get(level)!); // eslint-disable-line } let commentPadding = levelLength.size; for (const [, length] of levelLength) { commentPadding += length; } if (paddingMap.has(getFieldChildName(sep))) { commentPadding -= 1; } return [paddingMap, commentPadding]; } export class ThriftFormatter extends PureThriftFormatter { private data: ThriftData; private document: ThriftParserNS.DocumentContext; private fieldCommentPadding = 0; private fieldAlignByAssignPadding = 0; private fieldAlignByFieldPaddingMap: Map<string, number> = new Map(); private lastTokenIndex = -1; constructor(data: ThriftData) { super(); this.data = data; this.document = data.document; } format(): string { this.patch(); return this.formatNode(this.document); } public patch() { if (this._option.patchRequired) { walkNode(this.document, patchFieldRequired); } if (this._option.patchSeparator) { walkNode(this.document, patchFieldListSeparator); walkNode(this.document, patchRemoveLastListSeparator); } } protected beforeSubblocks(subblocks: ParseTree[]): void { if (this._option.alignByField) { const [paddingMap, commentPadding] = calcFieldAlignByFieldPaddingMap(subblocks); paddingMap.forEach((value, key, m) => {m.set(key, this.calcAddIndentPadding(value))}); this.fieldAlignByFieldPaddingMap = paddingMap; this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding); } else if (this._option.alignByAssign) { const [alignPadding, commentPadding] = calcFieldAlignByAssignPadding(subblocks); this.fieldAlignByAssignPadding = this.calcAddIndentPadding(alignPadding); this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding); } if (this._option.keepComment && this.fieldCommentPadding === 0) { const commentPadding = calcSubBlocksCommentPadding(subblocks); this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding); } } protected afterSubblocks(_: ParseTree[]): void { this.fieldAlignByAssignPadding = 0; this.fieldAlignByFieldPaddingMap = new Map(); this.fieldCommentPadding = 0; } protected afterBlockNode(_: ParseTree): void { this.addTailComment(); } protected beforeProcessNode(n: ParseTree): void { this.addAlignPadding(n) } protected get currentLine() :string { if (this.newlineCounter > 0) { return ''; } const parts = this.out.split('\n'); const cur = parts[parts.length -1]; return cur; } private calcAddIndentPadding(padding:number) : number { if (padding > 0) { padding += this._option.indent; } return padding; } private addAlignPadding(n: ParseTree): void { if (!isFieldOrEnumField(n.parent)) { return; } if (this._option.alignByField && this.fieldAlignByFieldPaddingMap.size > 0) { const name = getFieldChildName(n); const padding = this.fieldAlignByFieldPaddingMap.get(name); if (padding && padding > 0) { this.padding(padding); } return; } if (this._option.alignByAssign && isToken(n, '=') ) { this.padding(this.fieldAlignByAssignPadding); return; } return; } private padding(padding: number, pad = ' ') { if (padding > 0) { padding = padding - this.currentLine.length; if (padding > 0) { this.appendCurrentLine(pad.repeat(padding)); } } } private addInlineComments(node: TerminalNode) { if (!this._option.keepComment) { return; } if (isFakeNode(node)) { return; } const tokenIndex = node.symbol.tokenIndex; const comments = []; const tokens = this.data.tokens.getTokens(); for (const token of tokens.slice(this.lastTokenIndex + 1)) { if (token.channel != CommentChannel) { continue; } if (token.tokenIndex < tokenIndex) { comments.push(token); } } for (const token of comments) { if (token.tokenIndex > 0 && token.type == ThriftParser.ML_COMMENT) { this.newline(2); } if (token.text === undefined) { return; } // TODO: 确认是否需要 clean indent; this.pushCurrentIndent(); const text = token.text; this.append(text.trim()); const lastLine = token.line + text.split('\n').length - 1; const lineDiff = node.symbol.line - lastLine; const isTight = token.type == ThriftParser.SL_COMMENT || isEOF(node) || (0 < lineDiff && lineDiff <= 1); if (isTight) { this.newline(); } else { this.newline(2); } } this.lastTokenIndex = tokenIndex; } private addTailComment() { if (!this._option.keepComment) { return; } if (this.lastTokenIndex === -1) { return; } const tokens = this.data.tokens.getTokens(); const lastToken = tokens[this.lastTokenIndex]; const comments = []; for (const token of tokens.slice(this.lastTokenIndex + 1)) { if (token.line != lastToken.line) { break; } if (token.channel != CommentChannel) { continue; } comments.push(token); } if (comments.length > 0) { const comment = comments[0]; if (comment.text === undefined) { return; } // align comment if (this.fieldCommentPadding > 0) { this.padding(this.fieldCommentPadding, ' '); } else { this.appendCurrentLine(' '); } this.appendCurrentLine(comment.text.trim()); this.append(''); this.lastTokenIndex = comment.tokenIndex; } } protected TerminalNode(n: TerminalNode) { if (this.newlineCounter > 0) { this.addTailComment(); } this.addInlineComments(n); super.TerminalNode(n); } }