github.com/getgauge/gauge@v1.6.9/api/lang/diagnostics.go (about) 1 /*---------------------------------------------------------------- 2 * Copyright (c) ThoughtWorks, Inc. 3 * Licensed under the Apache License, Version 2.0 4 * See LICENSE in the project root for license information. 5 *----------------------------------------------------------------*/ 6 7 package lang 8 9 import ( 10 "context" 11 "fmt" 12 "sync" 13 14 "github.com/getgauge/common" 15 gm "github.com/getgauge/gauge-proto/go/gauge_messages" 16 "github.com/getgauge/gauge/gauge" 17 "github.com/getgauge/gauge/parser" 18 "github.com/getgauge/gauge/util" 19 "github.com/getgauge/gauge/validation" 20 "github.com/sourcegraph/go-langserver/pkg/lsp" 21 "github.com/sourcegraph/jsonrpc2" 22 ) 23 24 // Diagnostics lock ensures only one goroutine publishes diagnostics at a time. 25 var diagnosticsLock sync.Mutex 26 27 // isInQueue ensures that only one other goroutine waits for the diagnostic lock. 28 // Since diagnostics are published for all files, multiple threads need not wait to publish diagnostics. 29 var isInQueue = false 30 31 func publishDiagnostics(ctx context.Context, conn jsonrpc2.JSONRPC2) { 32 defer recoverPanic(nil) 33 if !isInQueue { 34 isInQueue = true 35 36 diagnosticsLock.Lock() 37 defer diagnosticsLock.Unlock() 38 39 isInQueue = false 40 41 diagnosticsMap, err := getDiagnostics() 42 if err != nil { 43 logError(nil, "Unable to publish diagnostics, error : %s", err.Error()) 44 return 45 } 46 for uri, diagnostics := range diagnosticsMap { 47 err := publishDiagnostic(uri, diagnostics, conn, ctx) 48 if err != nil { 49 logError(nil, "Unable to publish diagnostics for %s, error : %s", uri, err.Error()) 50 } 51 } 52 } 53 } 54 55 func publishDiagnostic(uri lsp.DocumentURI, diagnostics []lsp.Diagnostic, conn jsonrpc2.JSONRPC2, ctx context.Context) error { 56 params := lsp.PublishDiagnosticsParams{URI: uri, Diagnostics: diagnostics} 57 return conn.Notify(ctx, "textDocument/publishDiagnostics", params) 58 } 59 60 func getDiagnostics() (map[lsp.DocumentURI][]lsp.Diagnostic, error) { 61 diagnostics := make(map[lsp.DocumentURI][]lsp.Diagnostic) 62 conceptDictionary, err := validateConcepts(diagnostics) 63 if err != nil { 64 return nil, err 65 } 66 if err = validateSpecs(conceptDictionary, diagnostics); err != nil { 67 return nil, err 68 } 69 return diagnostics, nil 70 } 71 72 func createValidationDiagnostics(errors []error, diagnostics map[lsp.DocumentURI][]lsp.Diagnostic) { 73 for _, err := range errors { 74 uri := util.ConvertPathToURI(err.(validation.StepValidationError).FileName()) 75 s := err.(validation.StepValidationError).Step() 76 d := createDiagnostic(uri, err.(validation.StepValidationError).Message(), s.LineNo-1, s.LineSpanEnd-1, 1) 77 if err.(validation.StepValidationError).ErrorType() == gm.StepValidateResponse_STEP_IMPLEMENTATION_NOT_FOUND { 78 d.Code = err.(validation.StepValidationError).Suggestion() 79 } 80 diagnostics[uri] = append(diagnostics[uri], d) 81 } 82 } 83 84 func validateSpecifications(specs []*gauge.Specification, conceptDictionary *gauge.ConceptDictionary) []error { 85 if lRunner.runner == nil { 86 return []error{} 87 } 88 vErrs := validation.NewValidator(specs, lRunner.runner, conceptDictionary).Validate() 89 return validation.FilterDuplicates(vErrs) 90 } 91 92 func validateSpecs(conceptDictionary *gauge.ConceptDictionary, diagnostics map[lsp.DocumentURI][]lsp.Diagnostic) error { 93 specFiles := util.GetSpecFiles(util.GetSpecDirs()) 94 specs := make([]*gauge.Specification, 0) 95 for _, specFile := range specFiles { 96 uri := util.ConvertPathToURI(specFile) 97 if _, ok := diagnostics[uri]; !ok { 98 diagnostics[uri] = make([]lsp.Diagnostic, 0) 99 } 100 content, err := getContentFromFileOrDisk(specFile) 101 if err != nil { 102 return fmt.Errorf("unable to read file %s", err) 103 } 104 spec, res, err := new(parser.SpecParser).Parse(content, conceptDictionary, specFile) 105 if err != nil { 106 return err 107 } 108 createDiagnostics(res, diagnostics) 109 if res.Ok { 110 specs = append(specs, spec) 111 } 112 } 113 createValidationDiagnostics(validateSpecifications(specs, conceptDictionary), diagnostics) 114 return nil 115 } 116 117 func validateConcepts(diagnostics map[lsp.DocumentURI][]lsp.Diagnostic) (*gauge.ConceptDictionary, error) { 118 conceptFiles := util.GetConceptFiles() 119 conceptDictionary := gauge.NewConceptDictionary() 120 for _, conceptFile := range conceptFiles { 121 uri := util.ConvertPathToURI(conceptFile) 122 if _, ok := diagnostics[uri]; !ok { 123 diagnostics[uri] = make([]lsp.Diagnostic, 0) 124 } 125 content, err := getContentFromFileOrDisk(conceptFile) 126 if err != nil { 127 return nil, fmt.Errorf("unable to read file %s", err) 128 } 129 cpts, pRes := new(parser.ConceptParser).Parse(content, conceptFile) 130 pErrs, err := parser.AddConcept(cpts, conceptFile, conceptDictionary) // nolint 131 if err != nil { 132 return nil, err 133 } 134 pRes.ParseErrors = append(pRes.ParseErrors, pErrs...) 135 createDiagnostics(pRes, diagnostics) 136 } 137 createDiagnostics(parser.ValidateConcepts(conceptDictionary), diagnostics) 138 return conceptDictionary, nil 139 } 140 141 func createDiagnostics(res *parser.ParseResult, diagnostics map[lsp.DocumentURI][]lsp.Diagnostic) { 142 for _, err := range res.ParseErrors { 143 uri := util.ConvertPathToURI(err.FileName) 144 diagnostics[uri] = append(diagnostics[uri], createDiagnostic(uri, err.Message, err.LineNo-1, err.SpanEnd-1, 1)) 145 } 146 for _, warning := range res.Warnings { 147 uri := util.ConvertPathToURI(warning.FileName) 148 diagnostics[uri] = append(diagnostics[uri], createDiagnostic(uri, warning.Message, warning.LineNo-1, warning.LineSpanEnd-1, 2)) 149 } 150 } 151 152 func createDiagnostic(uri lsp.DocumentURI, message string, line int, lineEnd int, severity lsp.DiagnosticSeverity) lsp.Diagnostic { 153 endChar := 10000 154 if isOpen(uri) { 155 endChar = len(getLine(uri, line)) 156 } 157 return lsp.Diagnostic{ 158 Range: lsp.Range{ 159 Start: lsp.Position{Line: line, Character: 0}, 160 End: lsp.Position{Line: lineEnd, Character: endChar}, 161 }, 162 Message: message, 163 Severity: severity, 164 } 165 } 166 167 func getContentFromFileOrDisk(fileName string) (string, error) { 168 uri := util.ConvertPathToURI(fileName) 169 if isOpen(uri) { 170 return getContent(uri), nil 171 } else { 172 return common.ReadFileContents(fileName) 173 } 174 }