Skip to content

Commit

Permalink
Merge pull request #43 from lighttiger2505/add-document-format-option
Browse files Browse the repository at this point in the history
Add document format options
  • Loading branch information
lighttiger2505 authored Jan 28, 2021
2 parents 6046f5f + f8d27e9 commit 18f3470
Show file tree
Hide file tree
Showing 23 changed files with 205 additions and 88 deletions.
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Config struct {

func newConfig() *Config {
cfg := &Config{}
cfg.LowercaseKeywords = false
return cfg
}

Expand Down
12 changes: 10 additions & 2 deletions internal/formatter/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ func Format(text string, params lsp.DocumentFormattingParams) ([]lsp.TextEdit, e
Line: parsed.End().Line,
Character: parsed.End().Col,
}
formatted := Eval(parsed, &formatEnvironment{})
env := &formatEnvironment{
options: params.Options,
}
formatted := Eval(parsed, env)

res := []lsp.TextEdit{
{
Expand All @@ -44,6 +47,7 @@ func Format(text string, params lsp.DocumentFormattingParams) ([]lsp.TextEdit, e
type formatEnvironment struct {
reader *astutil.NodeReader
indentLevel int
options lsp.FormattingOptions
}

func (e *formatEnvironment) indentLevelReset() {
Expand All @@ -59,9 +63,13 @@ func (e *formatEnvironment) indentLevelDown() {
}

func (e *formatEnvironment) genIndent() []ast.Node {
indent := whiteSpaceNodes(int(e.options.TabSize))
if !e.options.InsertSpaces {
indent = []ast.Node{tabNode}
}
nodes := []ast.Node{}
for i := 0; i < e.indentLevel; i++ {
nodes = append(nodes, indentNode)
nodes = append(nodes, indent...)
}
return nodes
}
Expand Down
10 changes: 9 additions & 1 deletion internal/formatter/formatutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ var whitespaceNode = ast.NewItem(&token.Token{
Value: " ",
})

func whiteSpaceNodes(num int) []ast.Node {
res := make([]ast.Node, num)
for i := 0; i < num; i++ {
res[i] = whitespaceNode
}
return res
}

var linebreakNode = ast.NewItem(&token.Token{
Kind: token.Whitespace,
Value: "\n",
})

var indentNode = ast.NewItem(&token.Token{
var tabNode = ast.NewItem(&token.Token{
Kind: token.Whitespace,
Value: "\t",
})
Expand Down
215 changes: 131 additions & 84 deletions internal/handler/format_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handler

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -11,62 +12,89 @@ import (
"github.com/lighttiger2505/sqls/internal/lsp"
)

func TestFormatting(t *testing.T) {
var formattingOptionTab = lsp.FormattingOptions{
TabSize: 0.0,
InsertSpaces: false,
TrimTrailingWhitespace: false,
InsertFinalNewline: false,
TrimFinalNewlines: false,
}

var formattingOptionIndentSpace2 = lsp.FormattingOptions{
TabSize: 2.0,
InsertSpaces: true,
TrimTrailingWhitespace: false,
InsertFinalNewline: false,
TrimFinalNewlines: false,
}

var formattingOptionIndentSpace4 = lsp.FormattingOptions{
TabSize: 4.0,
InsertSpaces: true,
TrimTrailingWhitespace: false,
InsertFinalNewline: false,
TrimFinalNewlines: false,
}

type formattingTestCase struct {
name string
input string
want string
}

func testFormatting(t *testing.T, testCases []formattingTestCase, options lsp.FormattingOptions) {
tx := newTestContext()
tx.initServer(t)
defer tx.tearDown()

uri := "file:///Users/octref/Code/css-test/test.sql"
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
// Open dummy file
didOpenParams := lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: uri,
LanguageID: "sql",
Version: 0,
Text: tt.input,
},
}
if err := tx.conn.Call(tx.ctx, "textDocument/didOpen", didOpenParams, nil); err != nil {
t.Fatal("conn.Call textDocument/didOpen:", err)
}
tx.testFile(t, didOpenParams.TextDocument.URI, didOpenParams.TextDocument.Text)
// Create completion params
formattingParams := lsp.DocumentFormattingParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: uri,
},
Options: options,
WorkDoneProgressParams: lsp.WorkDoneProgressParams{
WorkDoneToken: nil,
},
}

type formattingTestCase struct {
name string
input string
want string
var got []lsp.TextEdit
if err := tx.conn.Call(tx.ctx, "textDocument/formatting", formattingParams, &got); err != nil {
t.Fatal("conn.Call textDocument/formatting:", err)
}
if diff := cmp.Diff(tt.want, got[0].NewText); diff != "" {
t.Errorf("unmatch (- want, + got):\n%s", diff)
t.Errorf("unmatch\nwant: %q\ngot : %q", tt.want, got[0].NewText)
}
})
}
}

testDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
testFileInfos, err := ioutil.ReadDir("testdata")
func TestFormattingBase(t *testing.T) {
testCase, err := loadFormatTestCaseByTestdata("format")
if err != nil {
t.Fatal(err)
}
testFormatting(t, testCase, formattingOptionTab)
}

testCase := []formattingTestCase{}

// Add golden file test
const (
inputFileSuffix = ".input.sql"
goldenFileSuffix = ".golden.sql"
)
for _, testFileInfo := range testFileInfos {
inputFileName := testFileInfo.Name()
if !strings.HasSuffix(inputFileName, inputFileSuffix) {
continue
}

testName := testFileInfo.Name()[:len(inputFileName)-len(inputFileSuffix)]
inputPath := filepath.Join(testDir, "testdata", inputFileName)
goldenPath := filepath.Join(testDir, "testdata", testName+goldenFileSuffix)

input, err := ioutil.ReadFile(inputPath)
if err != nil {
t.Errorf("Cannot read input file, Path=%s, Err=%+v", inputPath, err)
continue
}
golden, err := ioutil.ReadFile(goldenPath)
if err != nil {
t.Errorf("Cannot read input file, Path=%s, Err=%+v", goldenPath, err)
continue
}
testCase = append(testCase, formattingTestCase{
name: testName,
input: string(input),
want: string(golden)[:len(string(golden))-len("\n")],
})
}

func TestFormattingMinimal(t *testing.T) {
// Add minimal case test
minimalTestCase := []formattingTestCase{
{
Expand Down Expand Up @@ -105,48 +133,67 @@ func TestFormatting(t *testing.T) {
want: "1,\n2,\n3,\n4",
},
}
testCase = append(testCase, minimalTestCase...)
testFormatting(t, minimalTestCase, formattingOptionTab)
}

for _, tt := range testCase {
t.Run(tt.name, func(t *testing.T) {
// Open dummy file
didOpenParams := lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: uri,
LanguageID: "sql",
Version: 0,
Text: tt.input,
},
}
if err := tx.conn.Call(tx.ctx, "textDocument/didOpen", didOpenParams, nil); err != nil {
t.Fatal("conn.Call textDocument/didOpen:", err)
}
tx.testFile(t, didOpenParams.TextDocument.URI, didOpenParams.TextDocument.Text)
// Create completion params
formattingParams := lsp.DocumentFormattingParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: uri,
},
Options: lsp.FormattingOptions{
TabSize: 0.0,
InsertSpaces: false,
TrimTrailingWhitespace: false,
InsertFinalNewline: false,
TrimFinalNewlines: false,
},
WorkDoneProgressParams: lsp.WorkDoneProgressParams{
WorkDoneToken: nil,
},
}
func TestFormattingWithOptionSpace2(t *testing.T) {
testCase, err := loadFormatTestCaseByTestdata("format_option_space2")
if err != nil {
t.Fatal(err)
}
testFormatting(t, testCase, formattingOptionIndentSpace2)
}

var got []lsp.TextEdit
if err := tx.conn.Call(tx.ctx, "textDocument/formatting", formattingParams, &got); err != nil {
t.Fatal("conn.Call textDocument/formatting:", err)
}
if diff := cmp.Diff(tt.want, got[0].NewText); diff != "" {
t.Errorf("unmatch (- want, + got):\n%s", diff)
t.Errorf("unmatch\nwant: %q\ngot : %q", tt.want, got[0].NewText)
}
func TestFormattingWithOptionSpace4(t *testing.T) {
testCase, err := loadFormatTestCaseByTestdata("format_option_space4")
if err != nil {
t.Fatal(err)
}
testFormatting(t, testCase, formattingOptionIndentSpace4)
}

func loadFormatTestCaseByTestdata(targetDir string) ([]formattingTestCase, error) {
packageDir, err := os.Getwd()
if err != nil {
return nil, err
}
testDir := filepath.Join(packageDir, "testdata", targetDir)
testFileInfos, err := ioutil.ReadDir(testDir)
if err != nil {
return nil, err
}

testCase := []formattingTestCase{}
const (
inputFileSuffix = ".input.sql"
goldenFileSuffix = ".golden.sql"
)

for _, testFileInfo := range testFileInfos {
inputFileName := testFileInfo.Name()
if !strings.HasSuffix(inputFileName, inputFileSuffix) {
continue
}

testName := testFileInfo.Name()[:len(inputFileName)-len(inputFileSuffix)]
inputPath := filepath.Join(testDir, inputFileName)
goldenPath := filepath.Join(testDir, testName+goldenFileSuffix)

input, err := ioutil.ReadFile(inputPath)
if err != nil {
return nil, fmt.Errorf("Cannot read input file, Path=%s, Err=%+v", inputPath, err)
continue
}
golden, err := ioutil.ReadFile(goldenPath)
if err != nil {
return nil, fmt.Errorf("Cannot read input file, Path=%s, Err=%+v", goldenPath, err)
continue
}
testCase = append(testCase, formattingTestCase{
name: testName,
input: string(input),
want: string(golden)[:len(string(golden))-len("\n")],
})
}
return testCase, nil
}
3 changes: 2 additions & 1 deletion internal/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ func (s *Server) getConfig() *config.Config {
}

func validConfig(cfg *config.Config) bool {
if cfg != nil && len(cfg.Connections) > 0 {
// if cfg != nil && len(cfg.Connections) > 0 {
if cfg != nil {
return true
}
return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
select
a,
b as bb,
c
from
table
join (
select
a * 2 as a
from
new_table
) other
on table.a = other.a
where
c is true
and b between 3
and 4
or d is 'blue'
limit 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
select a, b as bb,c from table
join (select a * 2 as a from new_table) other
on table.a = other.a
where c is true
and b between 3 and 4
or d is 'blue'
limit 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
select
a,
b as bb,
c
from
table
join (
select
a * 2 as a
from
new_table
) other
on table.a = other.a
where
c is true
and b between 3
and 4
or d is 'blue'
limit 10
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
select a, b as bb,c from table
join (select a * 2 as a from new_table) other
on table.a = other.a
where c is true
and b between 3 and 4
or d is 'blue'
limit 10

0 comments on commit 18f3470

Please sign in to comment.