Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
},
"dependencies": {
"argparse": "^2.0.1",
"nearley": "^2.20.1"
"nearley": "^2.20.1",
"picocolors": "^1.1.1"
},
"devDependencies": {
"@babel/cli": "^7.10.4",
Expand Down
15 changes: 15 additions & 0 deletions src/FormatOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ export type FunctionCase = KeywordCase;

export type LogicalOperatorNewline = 'before' | 'after';

export type ColorKeys =
| 'keyword'
| 'operator'
| 'comment'
| 'string'
| 'number'
| 'function'
| 'parenthesis'
| 'identifier'
| 'dataType';

export interface FormatOptions {
tabWidth: number;
useTabs: boolean;
Expand All @@ -26,4 +37,8 @@ export interface FormatOptions {
newlineBeforeSemicolon: boolean;
params?: ParamItems | string[];
paramTypes?: ParamTypes;
compactParenthesis: boolean;
maxLengthInParenthesis?: number;
colors: boolean;
colorsMap: Record<ColorKeys, (input: string) => string>;
}
68 changes: 68 additions & 0 deletions src/formatter/BlockLayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { stripColors } from '../utils.js';
import Indentation from './Indentation.js';
import Layout, { WS } from './Layout.js';

export default class BlockLayout extends Layout {
private line = 0;
private length = 0;

// internal buffer for service tokens
private buffer: (WS | string)[] = [];

constructor(
indentation: Indentation,
private expressionWidth: number,
private colorized: boolean
) {
super(indentation);
}

public add(...items: (WS | string)[]) {
for (const item of items) {
// We add a comma as a service token so that the transfer of a comma to
// a new line is not a matter of chance, and it always
// remains on the same line as the element before it.
if (typeof item !== 'string' || item === ',') {
this.buffer.push(item);
continue;
}

const forceInsert = this.length === 0;
const insertCurrLineBuff = this.buffer.reduce(
(ret, service_item) => this.addLengthCurrentLine(service_item) && ret,
true
);
const insertCurrLine = this.addLengthCurrentLine(item) && insertCurrLineBuff;

if (insertCurrLine || forceInsert) {
super.add(...this.buffer, item);
} else {
super.add(...this.buffer, WS.NEWLINE, WS.INDENT, item);
}

this.buffer = [];

if (!insertCurrLine) {
this.line++;
this.length = 0;
}
}
}

private addLengthCurrentLine(item: WS | string) {
item = this.colorized && typeof item === 'string' ? stripColors(item) : item;

if (typeof item === 'string') {
this.length += item.length;
return this.length <= this.expressionWidth;
}

if (item === WS.NO_NEWLINE || item === WS.NO_SPACE) {
this.length--;
return true;
}

this.length++;
return this.length <= this.expressionWidth;
}
}
141 changes: 107 additions & 34 deletions src/formatter/ExpressionFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FormatOptions } from '../FormatOptions.js';
import * as regex from '../lexer/regexFactory.js';
import { ColorKeys, FormatOptions } from '../FormatOptions.js';
import { equalizeWhitespace, isMultiline, last } from '../utils.js';
import { limitNodesByType, limitWithComment } from './LimitNodes.js';

import Params from './Params.js';
import { isTabularStyle } from './config.js';
Expand Down Expand Up @@ -36,6 +38,7 @@ import {
import Layout, { WS } from './Layout.js';
import toTabularFormat, { isTabularToken } from './tabularStyle.js';
import InlineLayout, { InlineLayoutError } from './InlineLayout.js';
import BlockLayout from './BlockLayout.js';

interface ExpressionFormatterParams {
cfg: FormatOptions;
Expand Down Expand Up @@ -156,7 +159,7 @@ export default class ExpressionFormatter {

private formatParameterizedDataType(node: ParameterizedDataTypeNode) {
this.withComments(node.dataType, () => {
this.layout.add(this.showDataType(node.dataType));
this.layout.add(this.colorize('dataType', this.showDataType(node.dataType)));
});
this.formatNode(node.parenthesis);
}
Expand All @@ -166,13 +169,13 @@ export default class ExpressionFormatter {

switch (node.array.type) {
case NodeType.data_type:
formattedArray = this.showDataType(node.array);
formattedArray = this.colorize('dataType', this.showDataType(node.array));
break;
case NodeType.keyword:
formattedArray = this.showKw(node.array);
formattedArray = this.colorize('keyword', this.showKw(node.array));
break;
default:
formattedArray = this.showIdentifier(node.array);
formattedArray = this.colorize('identifier', this.showIdentifier(node.array));
break;
}

Expand All @@ -190,27 +193,66 @@ export default class ExpressionFormatter {
}

private formatParenthesis(node: ParenthesisNode) {
const inlineLayout = this.formatInlineExpression(node.children);
const maxLength = this.cfg.maxLengthInParenthesis;
const limit = limitNodesByType(
node.children,
NodeType.parameter,
limitWithComment,
0,
maxLength
);

this.params.addPositionalParameterIndex(limit.skippedFront);

const openParen = this.colorize('parenthesis', node.openParen);
const closeParen = this.colorize('parenthesis', node.closeParen);

if (this.cfg.compactParenthesis) {
const singleIndent = this.layout.indentation.getSingleIndent();
// Because the bracket is on the same line,
// we have to align the text one character less than necessary.
// I don't know how else to achieve the same effect with API library.
const partIndent = singleIndent.length > 1 ? singleIndent.slice(0, -1) : singleIndent;

if (inlineLayout) {
this.layout.add(node.openParen);
this.layout.add(...inlineLayout.getLayoutItems());
this.layout.add(WS.NO_SPACE, node.closeParen, WS.SPACE);
this.layout.add(openParen, partIndent);
this.layout.indentation.increaseBlockLevel();

const elements = this.formatBlockExpression(limit.nodes).getLayoutItems();
const hasNewline = elements.some(item => item === WS.NEWLINE);
this.layout.add(...elements);

this.layout.indentation.decreaseBlockLevel();

if (hasNewline) {
this.layout.add(WS.NEWLINE, WS.INDENT);
}

this.layout.add(closeParen, WS.SPACE);
} else {
this.layout.add(node.openParen, WS.NEWLINE);
const inlineLayout = this.formatInlineExpression(limit.nodes);

if (isTabularStyle(this.cfg)) {
this.layout.add(WS.INDENT);
this.layout = this.formatSubExpression(node.children);
if (inlineLayout) {
this.layout.add(openParen);
this.layout.add(...inlineLayout.getLayoutItems());
this.layout.add(WS.NO_SPACE, closeParen, WS.SPACE);
} else {
this.layout.indentation.increaseBlockLevel();
this.layout.add(WS.INDENT);
this.layout = this.formatSubExpression(node.children);
this.layout.indentation.decreaseBlockLevel();
}
this.layout.add(openParen, WS.NEWLINE);

this.layout.add(WS.NEWLINE, WS.INDENT, node.closeParen, WS.SPACE);
if (isTabularStyle(this.cfg)) {
this.layout.add(WS.INDENT);
this.layout = this.formatSubExpression(limit.nodes);
} else {
this.layout.indentation.increaseBlockLevel();
this.layout.add(WS.INDENT);
this.layout = this.formatSubExpression(limit.nodes);
this.layout.indentation.decreaseBlockLevel();
}

this.layout.add(WS.NEWLINE, WS.INDENT, closeParen, WS.SPACE);
}
}

this.params.addPositionalParameterIndex(limit.skippedBack);
}

private formatBetweenPredicate(node: BetweenPredicateNode) {
Expand Down Expand Up @@ -318,15 +360,20 @@ export default class ExpressionFormatter {
}

private formatLiteral(node: LiteralNode) {
this.layout.add(node.text, WS.SPACE);
const isString = /^("|')/.test(node.text) && 'string';
this.layout.add(this.colorize(isString || 'number', node.text), WS.SPACE);
}

private formatIdentifier(node: IdentifierNode) {
this.layout.add(this.showIdentifier(node), WS.SPACE);
this.layout.add(this.colorize('identifier', this.showIdentifier(node)), WS.SPACE);
}

private formatParameter(node: ParameterNode) {
this.layout.add(this.params.get(node), WS.SPACE);
const param = this.params.get(node);
const isString = /^("|')/.test(param) && 'string';
const isNumber = regex.number(true).test(param) && 'number';

this.layout.add(this.colorize(isString || isNumber || undefined, param), WS.SPACE);
}

private formatOperator({ text }: OperatorNode) {
Expand Down Expand Up @@ -367,24 +414,26 @@ export default class ExpressionFormatter {
}

private formatLineComment(node: LineCommentNode) {
const comment = this.colorize('comment', node.text);

if (isMultiline(node.precedingWhitespace || '')) {
this.layout.add(WS.NEWLINE, WS.INDENT, node.text, WS.MANDATORY_NEWLINE, WS.INDENT);
this.layout.add(WS.NEWLINE, WS.INDENT, comment, WS.MANDATORY_NEWLINE, WS.INDENT);
} else if (this.layout.getLayoutItems().length > 0) {
this.layout.add(WS.NO_NEWLINE, WS.SPACE, node.text, WS.MANDATORY_NEWLINE, WS.INDENT);
this.layout.add(WS.NO_NEWLINE, WS.SPACE, comment, WS.MANDATORY_NEWLINE, WS.INDENT);
} else {
// comment is the first item in code - no need to add preceding spaces
this.layout.add(node.text, WS.MANDATORY_NEWLINE, WS.INDENT);
this.layout.add(comment, WS.MANDATORY_NEWLINE, WS.INDENT);
}
}

private formatBlockComment(node: BlockCommentNode | DisableCommentNode) {
if (node.type === NodeType.block_comment && this.isMultilineBlockComment(node)) {
this.splitBlockComment(node.text).forEach(line => {
this.layout.add(WS.NEWLINE, WS.INDENT, line);
this.layout.add(WS.NEWLINE, WS.INDENT, this.colorize('comment', line));
});
this.layout.add(WS.NEWLINE, WS.INDENT);
} else {
this.layout.add(node.text, WS.SPACE);
this.layout.add(this.colorize('comment', node.text), WS.SPACE);
}
}

Expand Down Expand Up @@ -462,7 +511,7 @@ export default class ExpressionFormatter {
cfg: this.cfg,
dialectCfg: this.dialectCfg,
params: this.params,
layout: new InlineLayout(this.cfg.expressionWidth),
layout: new InlineLayout(this.cfg.expressionWidth, this.cfg.colors),
inline: true,
}).format(nodes);
} catch (e) {
Expand All @@ -480,6 +529,24 @@ export default class ExpressionFormatter {
}
}

private formatBlockExpression(nodes: AstNode[]): Layout {
return new ExpressionFormatter({
cfg: this.cfg,
dialectCfg: this.dialectCfg,
params: this.params,
layout: new BlockLayout(this.layout.indentation, this.cfg.expressionWidth, this.cfg.colors),
inline: true,
}).format(nodes);
}

private colorize(colorKey: ColorKeys | undefined, text: string): string {
if (!this.cfg.colors || !colorKey) {
return text;
}

return this.cfg.colorsMap[colorKey](text);
}

private formatKeywordNode(node: KeywordNode): void {
switch (node.tokenType) {
case TokenType.RESERVED_JOIN:
Expand Down Expand Up @@ -524,14 +591,17 @@ export default class ExpressionFormatter {
}

private formatDataType(node: DataTypeNode) {
this.layout.add(this.showDataType(node), WS.SPACE);
this.layout.add(this.colorize('dataType', this.showDataType(node)), WS.SPACE);
}

private showKw(node: KeywordNode): string {
if (isTabularToken(node.tokenType)) {
return toTabularFormat(this.showNonTabularKw(node), this.cfg.indentStyle);
return this.colorize(
'keyword',
toTabularFormat(this.showNonTabularKw(node), this.cfg.indentStyle)
);
} else {
return this.showNonTabularKw(node);
return this.colorize('keyword', this.showNonTabularKw(node));
}
}

Expand All @@ -549,9 +619,12 @@ export default class ExpressionFormatter {

private showFunctionKw(node: KeywordNode): string {
if (isTabularToken(node.tokenType)) {
return toTabularFormat(this.showNonTabularFunctionKw(node), this.cfg.indentStyle);
return this.colorize(
'function',
toTabularFormat(this.showNonTabularFunctionKw(node), this.cfg.indentStyle)
);
} else {
return this.showNonTabularFunctionKw(node);
return this.colorize('function', this.showNonTabularFunctionKw(node));
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/formatter/InlineLayout.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// eslint-disable-next-line max-classes-per-file
import { stripColors } from '../utils.js';
import Indentation from './Indentation.js';
import Layout, { WS } from './Layout.js';

Expand All @@ -16,7 +17,7 @@ export default class InlineLayout extends Layout {
// but only when there actually is a space to remove.
private trailingSpace = false;

constructor(private expressionWidth: number) {
constructor(private expressionWidth: number, private colorized: boolean) {
super(new Indentation('')); // no indentation in inline layout
}

Expand All @@ -30,6 +31,8 @@ export default class InlineLayout extends Layout {
}

private addToLength(item: WS | string) {
item = this.colorized && typeof item === 'string' ? stripColors(item) : item;

if (typeof item === 'string') {
this.length += item.length;
this.trailingSpace = false;
Expand Down
Loading