Skip to content
This repository was archived by the owner on Jun 15, 2021. It is now read-only.

Commit 5e0fff9

Browse files
committed
feat: match formatting indentation to terraform fmt (#86)
1 parent 7d41913 commit 5e0fff9

File tree

10 files changed

+334
-132
lines changed

10 files changed

+334
-132
lines changed

.vscode/launch.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@
1717
"request": "launch",
1818
"name": "Jest Current File",
1919
"program": "${workspaceFolder}/node_modules/.bin/jest",
20-
"args": ["${relativeFile}"],
21-
"console": "integratedTerminal",
22-
"internalConsoleOptions": "neverOpen",
23-
"disableOptimisticBPs": true,
20+
"args": ["${fileBasename}"],
2421
"outFiles": ["${workspaceFolder}/out/**"],
2522
"windows": {
2623
"program": "${workspaceFolder}/node_modules/jest/bin/jest"

docs/formatting.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Formatting Guidelines
2+
3+
Following the ones used by `terraform fmt`:
4+
5+
> https://www.terraform.io/docs/configuration/style.html
6+
7+
- Indent two spaces for each nesting level.
8+
- Inside a block, arguments go together first, then blocks (separated by a blank line each).
9+
- Align their equal sign according to the longest key.

gulpfile.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { BuildTasks } from "./scripts/gulp-build";
77
import { LinterTasks } from "./scripts/gulp-linter";
88
import { VSCodeTasks } from "./scripts/gulp-vscode";
99

10+
// TODO: replace all module-like files with classes
11+
1012
// Called by debugger before launching
1113
gulp.task("update-vscode", gulp.series([BuildTasks.compile, VSCodeTasks.copyFiles, VSCodeTasks.generatePackageJson]));
1214

src/binding/bound-nodes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ObjectMemberSyntax,
1010
BasePropertySyntax,
1111
} from "../parsing/syntax-nodes";
12-
import { Token } from "../scanning/tokens";
12+
import { TokenWithTrivia } from "../scanning/tokens";
1313

1414
export enum BoundKind {
1515
// Top level
@@ -147,7 +147,7 @@ export class BoundSecrets extends BaseBoundNode {
147147
}
148148

149149
export class BoundStringValue extends BaseBoundNode {
150-
public constructor(public readonly value: string, public readonly syntax: Token) {
150+
public constructor(public readonly value: string, public readonly syntax: TokenWithTrivia) {
151151
super(BoundKind.StringValue);
152152
}
153153
}

src/parsing/parser.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
import { DiagnosticBag } from "../util/diagnostics";
6-
import { Token, TokenKind, getTokenDescription } from "../scanning/tokens";
6+
import { Token, TokenKind, getTokenDescription, TokenWithTrivia } from "../scanning/tokens";
77
import {
88
DocumentSyntax,
99
VersionSyntax,
@@ -22,7 +22,9 @@ interface ParseContext {
2222
}
2323

2424
export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag): DocumentSyntax {
25-
const tokens = allTokens.filter(token => token.kind !== TokenKind.Comment && token.kind !== TokenKind.Unrecognized);
25+
const tokens = allTokens.filter(token => token.kind !== TokenKind.Unrecognized);
26+
const commentsAfter = extractCommentsAtEndOfFile();
27+
2628
const reportedErrors = Array<boolean>();
2729

2830
let index = 0;
@@ -35,7 +37,18 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
3537
});
3638
}
3739

38-
return new DocumentSyntax(versions, blocks);
40+
return new DocumentSyntax(versions, blocks, commentsAfter);
41+
42+
function extractCommentsAtEndOfFile(): ReadonlyArray<TokenWithTrivia> {
43+
let end = tokens.length - 1;
44+
while (end >= 0 && tokens[end].kind === TokenKind.Comment) {
45+
end -= 1;
46+
}
47+
48+
const result = tokens.slice(end + 1);
49+
tokens.splice(end + 1);
50+
return result;
51+
}
3952

4053
function parseTopLevelNode(context: ParseContext): void {
4154
const keywordKinds = [TokenKind.VersionKeyword, TokenKind.WorkflowKeyword, TokenKind.ActionKeyword];
@@ -66,14 +79,14 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
6679
}
6780
}
6881

69-
function parseVersion(version: Token, context: ParseContext): void {
82+
function parseVersion(version: TokenWithTrivia, context: ParseContext): void {
7083
const equal = eat(context, TokenKind.Equal);
7184
const integer = eat(context, TokenKind.IntegerLiteral);
7285

7386
versions.push(new VersionSyntax(version, equal, integer));
7487
}
7588

76-
function parseBlock(type: Token, context: ParseContext): void {
89+
function parseBlock(type: TokenWithTrivia, context: ParseContext): void {
7790
const name = eat(context, TokenKind.StringLiteral);
7891
const openBracket = eat(context, TokenKind.LeftCurlyBracket);
7992

@@ -118,7 +131,7 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
118131
return properties;
119132
}
120133

121-
function parseProperty(key: Token, context: ParseContext): BasePropertySyntax {
134+
function parseProperty(key: TokenWithTrivia, context: ParseContext): BasePropertySyntax {
122135
const equal = eat(context, TokenKind.Equal);
123136
const valueStart = eat(context, TokenKind.StringLiteral, TokenKind.LeftCurlyBracket, TokenKind.LeftSquareBracket);
124137

@@ -163,7 +176,7 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
163176
break;
164177
}
165178

166-
let comma: Token | undefined;
179+
let comma: TokenWithTrivia | undefined;
167180
if (isNext(TokenKind.Comma)) {
168181
comma = eat(context, TokenKind.Comma);
169182
}
@@ -197,16 +210,37 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
197210
return index < tokens.length && tokens[index].kind === kind;
198211
}
199212

200-
function eat(context: ParseContext, ...expected: TokenKind[]): Token {
213+
function eat(context: ParseContext, ...expected: TokenKind[]): TokenWithTrivia {
214+
const commentsBefore = eatComments();
215+
201216
while (true) {
202217
if (index >= tokens.length) {
203-
return missingToken(expected);
218+
return {
219+
commentsBefore,
220+
...missingToken(expected),
221+
};
204222
}
205223

206224
const current = tokens[index];
207225
if (expected.includes(current.kind)) {
208226
index += 1;
209-
return current;
227+
228+
if (index < tokens.length) {
229+
const commentAfter = tokens[index];
230+
if (commentAfter.kind === TokenKind.Comment && commentAfter.range.start.line === current.range.end.line) {
231+
index += 1;
232+
return {
233+
commentsBefore,
234+
...current,
235+
commentAfter,
236+
};
237+
}
238+
}
239+
240+
return {
241+
commentsBefore,
242+
...current,
243+
};
210244
}
211245

212246
let canBeHandledByParent = false;
@@ -217,7 +251,10 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
217251
}
218252

219253
if (canBeHandledByParent) {
220-
return missingToken(expected);
254+
return {
255+
commentsBefore,
256+
...missingToken(expected),
257+
};
221258
}
222259

223260
if (!reportedErrors[index]) {
@@ -229,6 +266,21 @@ export function parseTokens(allTokens: ReadonlyArray<Token>, bag: DiagnosticBag)
229266
}
230267
}
231268

269+
function eatComments(): ReadonlyArray<TokenWithTrivia> | undefined {
270+
let result: TokenWithTrivia[] | undefined;
271+
272+
while (index < tokens.length && tokens[index].kind === TokenKind.Comment) {
273+
if (!result) {
274+
result = [];
275+
}
276+
277+
result.push(tokens[index]);
278+
index += 1;
279+
}
280+
281+
return result;
282+
}
283+
232284
function missingToken(expected: TokenKind[]): Token {
233285
let missingIndex = index;
234286
const endOfFile = index >= tokens.length;

src/parsing/syntax-nodes.ts

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright 2019 Omar Tawfik. Please see LICENSE file at the root of this repository.
33
*/
44

5-
import { Token, TokenKind, getTokenDescription } from "../scanning/tokens";
5+
import { Token, TokenKind, getTokenDescription, TokenWithTrivia } from "../scanning/tokens";
66
import { Range } from "vscode-languageserver-types";
77
import { comparePositions } from "../util/ranges";
88

@@ -59,35 +59,40 @@ export class DocumentSyntax extends BaseSyntaxNode {
5959
public constructor(
6060
public readonly versions: ReadonlyArray<VersionSyntax>,
6161
public readonly blocks: ReadonlyArray<BlockSyntax>,
62+
public readonly commentsAfter: ReadonlyArray<TokenWithTrivia>,
6263
) {
6364
super(SyntaxKind.Document);
6465
}
6566

66-
public calculateRange(): Range {
67-
return combineRange(...this.versions, ...this.blocks);
67+
protected calculateRange(): Range {
68+
return combineRange(...this.versions, ...this.blocks, ...this.commentsAfter);
6869
}
6970
}
7071

7172
export class VersionSyntax extends BaseSyntaxNode {
72-
public constructor(public readonly version: Token, public readonly equal: Token, public readonly integer: Token) {
73+
public constructor(
74+
public readonly version: TokenWithTrivia,
75+
public readonly equal: TokenWithTrivia,
76+
public readonly integer: TokenWithTrivia,
77+
) {
7378
super(SyntaxKind.Version);
7479
assertTokenKind(version, TokenKind.VersionKeyword);
7580
assertTokenKind(equal, TokenKind.Equal);
7681
assertTokenKind(integer, TokenKind.IntegerLiteral);
7782
}
7883

79-
public calculateRange(): Range {
84+
protected calculateRange(): Range {
8085
return combineRange(this.version, this.equal, this.integer);
8186
}
8287
}
8388

8489
export class BlockSyntax extends BaseSyntaxNode {
8590
public constructor(
86-
public readonly type: Token,
87-
public readonly name: Token,
88-
public readonly openBracket: Token,
91+
public readonly type: TokenWithTrivia,
92+
public readonly name: TokenWithTrivia,
93+
public readonly openBracket: TokenWithTrivia,
8994
public readonly properties: ReadonlyArray<BasePropertySyntax>,
90-
public readonly closeBracket: Token,
95+
public readonly closeBracket: TokenWithTrivia,
9196
) {
9297
super(SyntaxKind.Block);
9398
assertTokenKind(type, TokenKind.ActionKeyword, TokenKind.WorkflowKeyword);
@@ -96,13 +101,17 @@ export class BlockSyntax extends BaseSyntaxNode {
96101
assertTokenKind(closeBracket, TokenKind.RightCurlyBracket);
97102
}
98103

99-
public calculateRange(): Range {
104+
protected calculateRange(): Range {
100105
return combineRange(this.type, this.name, this.openBracket, ...this.properties, this.closeBracket);
101106
}
102107
}
103108

104109
export abstract class BasePropertySyntax extends BaseSyntaxNode {
105-
protected constructor(kind: SyntaxKind, public readonly key: Token, public readonly equal: Token) {
110+
protected constructor(
111+
kind: SyntaxKind,
112+
public readonly key: TokenWithTrivia,
113+
public readonly equal: TokenWithTrivia,
114+
) {
106115
super(kind);
107116
assertTokenKind(
108117
key,
@@ -120,73 +129,77 @@ export abstract class BasePropertySyntax extends BaseSyntaxNode {
120129
}
121130

122131
export class StringPropertySyntax extends BasePropertySyntax {
123-
public constructor(key: Token, equal: Token, public readonly value: Token | undefined) {
132+
public constructor(key: TokenWithTrivia, equal: TokenWithTrivia, public readonly value: TokenWithTrivia | undefined) {
124133
super(SyntaxKind.StringProperty, key, equal);
125134
assertTokenKind(value, TokenKind.StringLiteral);
126135
}
127136

128-
public calculateRange(): Range {
137+
protected calculateRange(): Range {
129138
return combineRange(this.key, this.equal, this.value);
130139
}
131140
}
132141

133142
export class ArrayPropertySyntax extends BasePropertySyntax {
134143
public constructor(
135-
key: Token,
136-
equal: Token,
137-
public readonly openBracket: Token,
144+
key: TokenWithTrivia,
145+
equal: TokenWithTrivia,
146+
public readonly openBracket: TokenWithTrivia,
138147
public readonly items: ReadonlyArray<ArrayItemSyntax>,
139-
public readonly closeBracket: Token,
148+
public readonly closeBracket: TokenWithTrivia,
140149
) {
141150
super(SyntaxKind.ArrayProperty, key, equal);
142151
assertTokenKind(openBracket, TokenKind.LeftSquareBracket);
143152
assertTokenKind(closeBracket, TokenKind.RightSquareBracket);
144153
}
145154

146-
public calculateRange(): Range {
155+
protected calculateRange(): Range {
147156
return combineRange(this.openBracket, ...this.items, this.closeBracket);
148157
}
149158
}
150159

151160
export class ArrayItemSyntax extends BaseSyntaxNode {
152-
public constructor(public readonly value: Token, public readonly comma: Token | undefined) {
161+
public constructor(public readonly value: TokenWithTrivia, public readonly comma: TokenWithTrivia | undefined) {
153162
super(SyntaxKind.ArrayItem);
154163
assertTokenKind(value, TokenKind.StringLiteral);
155164
assertTokenKind(comma, TokenKind.Comma);
156165
}
157166

158-
public calculateRange(): Range {
167+
protected calculateRange(): Range {
159168
return combineRange(this.value, this.comma);
160169
}
161170
}
162171

163172
export class ObjectPropertySyntax extends BasePropertySyntax {
164173
public constructor(
165-
key: Token,
166-
equal: Token,
167-
public readonly openBracket: Token,
174+
key: TokenWithTrivia,
175+
equal: TokenWithTrivia,
176+
public readonly openBracket: TokenWithTrivia,
168177
public readonly members: ReadonlyArray<ObjectMemberSyntax>,
169-
public readonly closeBracket: Token,
178+
public readonly closeBracket: TokenWithTrivia,
170179
) {
171180
super(SyntaxKind.ObjectProperty, key, equal);
172181
assertTokenKind(openBracket, TokenKind.LeftCurlyBracket);
173182
assertTokenKind(closeBracket, TokenKind.RightCurlyBracket);
174183
}
175184

176-
public calculateRange(): Range {
185+
protected calculateRange(): Range {
177186
return combineRange(this.openBracket, ...this.members, this.closeBracket);
178187
}
179188
}
180189

181190
export class ObjectMemberSyntax extends BaseSyntaxNode {
182-
public constructor(public readonly name: Token, public readonly equal: Token, public readonly value: Token) {
191+
public constructor(
192+
public readonly name: TokenWithTrivia,
193+
public readonly equal: TokenWithTrivia,
194+
public readonly value: TokenWithTrivia,
195+
) {
183196
super(SyntaxKind.ObjectMember);
184197
assertTokenKind(name, TokenKind.Identifier);
185198
assertTokenKind(equal, TokenKind.Equal);
186199
assertTokenKind(value, TokenKind.StringLiteral);
187200
}
188201

189-
public calculateRange(): Range {
202+
protected calculateRange(): Range {
190203
return combineRange(this.name, this.equal, this.value);
191204
}
192205
}

src/scanning/tokens.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,8 @@ export interface Token {
105105
readonly range: Range;
106106
readonly text: string;
107107
}
108+
109+
export interface TokenWithTrivia extends Token {
110+
readonly commentsBefore?: ReadonlyArray<TokenWithTrivia>;
111+
readonly commentAfter?: TokenWithTrivia;
112+
}

0 commit comments

Comments
 (0)