• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brighterscript / #12929

13 Aug 2024 05:02PM UTC coverage: 86.193% (-1.7%) from 87.933%
#12929

push

web-flow
Merge 58ad447a2 into 0e968f1c3

10630 of 13125 branches covered (80.99%)

Branch coverage included in aggregate %.

6675 of 7284 new or added lines in 99 files covered. (91.64%)

84 existing lines in 18 files now uncovered.

12312 of 13492 relevant lines covered (91.25%)

26867.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.85
/src/bscPlugin/codeActions/CodeActionsProcessor.ts
1
import { CodeActionKind } from 'vscode-languageserver';
1✔
2
import { codeActionUtil } from '../../CodeActionUtil';
1✔
3
import type { DiagnosticMessageType } from '../../DiagnosticMessages';
4
import { DiagnosticCodeMap } from '../../DiagnosticMessages';
1✔
5
import type { BrsFile } from '../../files/BrsFile';
6
import type { BscFile } from '../../files/BscFile';
7
import type { XmlFile } from '../../files/XmlFile';
8
import type { BsDiagnostic, OnGetCodeActionsEvent } from '../../interfaces';
9
import { ParseMode } from '../../parser/Parser';
1✔
10
import { util } from '../../util';
1✔
11
import { isBrsFile } from '../../astUtils/reflection';
1✔
12

13
export class CodeActionsProcessor {
1✔
14
    public constructor(
15
        public event: OnGetCodeActionsEvent
10✔
16
    ) {
17

18
    }
19

20
    public process() {
21
        for (const diagnostic of this.event.diagnostics) {
10✔
22
            if (diagnostic.code === DiagnosticCodeMap.cannotFindName || diagnostic.code === DiagnosticCodeMap.cannotFindFunction) {
12✔
23
                this.suggestCannotFindName(diagnostic as any);
8✔
24
            } else if (diagnostic.code === DiagnosticCodeMap.classCouldNotBeFound) {
4!
25
                this.suggestClassImports(diagnostic as any);
×
26
            } else if (diagnostic.code === DiagnosticCodeMap.xmlComponentMissingExtendsAttribute) {
4✔
27
                this.addMissingExtends(diagnostic as any);
2✔
28
            }
29
        }
30
    }
31

32
    private suggestedImports = new Set<string>();
10✔
33

34
    /**
35
     * Generic import suggestion function. Shouldn't be called directly from the main loop, but instead called by more specific diagnostic handlers
36
     */
37
    private suggestImports(diagnostic: BsDiagnostic, key: string, files: BscFile[]) {
38
        //skip if we already have this suggestion
39
        if (this.suggestedImports.has(key) || !isBrsFile(this.event.file)) {
7!
40
            return;
×
41
        }
42

43
        this.suggestedImports.add(key);
7✔
44
        // eslint-disable-next-line @typescript-eslint/dot-notation
45
        const importStatements = this.event.file['_cachedLookups'].importStatements;
7✔
46
        //find the position of the first import statement, or the top of the file if there is none
47
        const insertPosition = importStatements[importStatements.length - 1]?.tokens.import?.location?.range?.start ?? util.createPosition(0, 0);
7✔
48

49
        //find all files that reference this function
50
        for (const file of files) {
7✔
51
            const destPath = util.sanitizePkgPath(file.destPath);
9✔
52
            this.event.codeActions.push(
9✔
53
                codeActionUtil.createCodeAction({
54
                    title: `import "${destPath}"`,
55
                    diagnostics: [diagnostic],
56
                    isPreferred: false,
57
                    kind: CodeActionKind.QuickFix,
58
                    changes: [{
59
                        type: 'insert',
60
                        filePath: this.event.file.srcPath,
61
                        position: insertPosition,
62
                        newText: `import "${destPath}"\n`
63
                    }]
64
                })
65
            );
66
        }
67
    }
68

69
    private suggestCannotFindName(diagnostic: DiagnosticMessageType<'cannotFindName'>) {
70
        //skip if not a BrighterScript file
71
        const file = this.event.program.getFile(diagnostic.location?.uri);
8!
72
        if (!file || (file as BrsFile).parseMode !== ParseMode.BrighterScript) {
8✔
73
            return;
1✔
74
        }
75
        const lowerName = (diagnostic.data.fullName ?? diagnostic.data.name).toLowerCase();
7!
76

77
        this.suggestImports(
7✔
78
            diagnostic,
79
            lowerName,
80
            [
81
                ...this.event.program.findFilesForFunction(lowerName),
82
                ...this.event.program.findFilesForClass(lowerName),
83
                ...this.event.program.findFilesForNamespace(lowerName),
84
                ...this.event.program.findFilesForEnum(lowerName)
85
            ]
86
        );
87
    }
88

89
    private suggestClassImports(diagnostic: DiagnosticMessageType<'classCouldNotBeFound'>) {
90
        //skip if not a BrighterScript file
NEW
91
        const file = this.event.program.getFile(diagnostic.location?.uri);
×
NEW
92
        if (!file || (file as BrsFile).parseMode !== ParseMode.BrighterScript) {
×
UNCOV
93
            return;
×
94
        }
95
        const lowerClassName = diagnostic.data.className.toLowerCase();
×
96
        this.suggestImports(
×
97
            diagnostic,
98
            lowerClassName,
99
            this.event.program.findFilesForClass(lowerClassName)
100
        );
101
    }
102

103
    private addMissingExtends(diagnostic: DiagnosticMessageType<'xmlComponentMissingExtendsAttribute'>) {
104
        const srcPath = this.event.file.srcPath;
2✔
105
        const { componentElement } = (this.event.file as XmlFile).parser.ast;
2✔
106
        //inject new attribute after the final attribute, or after the `<component` if there are no attributes
107
        const pos = (componentElement.attributes[componentElement.attributes.length - 1] ?? componentElement.tokens.startTagName)?.location?.range.end;
2!
108
        this.event.codeActions.push(
2✔
109
            codeActionUtil.createCodeAction({
110
                title: `Extend "Group"`,
111
                diagnostics: [diagnostic],
112
                isPreferred: true,
113
                kind: CodeActionKind.QuickFix,
114
                changes: [{
115
                    type: 'insert',
116
                    filePath: srcPath,
117
                    position: pos,
118
                    newText: ' extends="Group"'
119
                }]
120
            })
121
        );
122
        this.event.codeActions.push(
2✔
123
            codeActionUtil.createCodeAction({
124
                title: `Extend "Task"`,
125
                diagnostics: [diagnostic],
126
                kind: CodeActionKind.QuickFix,
127
                changes: [{
128
                    type: 'insert',
129
                    filePath: srcPath,
130
                    position: pos,
131
                    newText: ' extends="Task"'
132
                }]
133
            })
134
        );
135
        this.event.codeActions.push(
2✔
136
            codeActionUtil.createCodeAction({
137
                title: `Extend "ContentNode"`,
138
                diagnostics: [diagnostic],
139
                kind: CodeActionKind.QuickFix,
140
                changes: [{
141
                    type: 'insert',
142
                    filePath: srcPath,
143
                    position: pos,
144
                    newText: ' extends="ContentNode"'
145
                }]
146
            })
147
        );
148
    }
149
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc