brighterscript 0.70.3 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/DiagnosticMessages.d.ts +21 -3
- package/dist/DiagnosticMessages.js +21 -3
- package/dist/DiagnosticMessages.js.map +1 -1
- package/dist/LanguageServer.d.ts +24 -0
- package/dist/LanguageServer.js +46 -2
- package/dist/LanguageServer.js.map +1 -1
- package/dist/Program.js +1 -1
- package/dist/Program.js.map +1 -1
- package/dist/ProgramBuilder.d.ts +1 -1
- package/dist/Scope.d.ts +14 -1
- package/dist/Scope.js +58 -6
- package/dist/Scope.js.map +1 -1
- package/dist/astUtils/reflection.d.ts +4 -2
- package/dist/astUtils/reflection.js +11 -2
- package/dist/astUtils/reflection.js.map +1 -1
- package/dist/astUtils/visitors.d.ts +4 -2
- package/dist/astUtils/visitors.js.map +1 -1
- package/dist/bscPlugin/codeActions/CodeActionsProcessor.d.ts +72 -6
- package/dist/bscPlugin/codeActions/CodeActionsProcessor.js +392 -110
- package/dist/bscPlugin/codeActions/CodeActionsProcessor.js.map +1 -1
- package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js +575 -0
- package/dist/bscPlugin/codeActions/CodeActionsProcessor.spec.js.map +1 -1
- package/dist/bscPlugin/definition/DefinitionProvider.js +8 -0
- package/dist/bscPlugin/definition/DefinitionProvider.js.map +1 -1
- package/dist/bscPlugin/definition/DefinitionProvider.spec.js +84 -0
- package/dist/bscPlugin/definition/DefinitionProvider.spec.js.map +1 -1
- package/dist/bscPlugin/validation/BrsFileValidator.js +2 -1
- package/dist/bscPlugin/validation/BrsFileValidator.js.map +1 -1
- package/dist/bscPlugin/validation/ScopeValidator.d.ts +6 -0
- package/dist/bscPlugin/validation/ScopeValidator.js +70 -0
- package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
- package/dist/files/BrsFile.spec.js +251 -0
- package/dist/files/BrsFile.spec.js.map +1 -1
- package/dist/lexer/TokenKind.js +4 -2
- package/dist/lexer/TokenKind.js.map +1 -1
- package/dist/lsp/ProjectManager.d.ts +10 -0
- package/dist/lsp/ProjectManager.js +21 -4
- package/dist/lsp/ProjectManager.js.map +1 -1
- package/dist/lsp/ProjectManager.spec.js +30 -0
- package/dist/lsp/ProjectManager.spec.js.map +1 -1
- package/dist/parser/AstNode.spec.js +21 -0
- package/dist/parser/AstNode.spec.js.map +1 -1
- package/dist/parser/Expression.d.ts +26 -2
- package/dist/parser/Expression.js +47 -6
- package/dist/parser/Expression.js.map +1 -1
- package/dist/parser/Parser.d.ts +7 -1
- package/dist/parser/Parser.js +200 -20
- package/dist/parser/Parser.js.map +1 -1
- package/dist/parser/Parser.spec.js +418 -0
- package/dist/parser/Parser.spec.js.map +1 -1
- package/dist/parser/Statement.d.ts +20 -0
- package/dist/parser/Statement.js +51 -1
- package/dist/parser/Statement.js.map +1 -1
- package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js +70 -0
- package/dist/parser/tests/expression/AssociativeArrayLiterals.spec.js.map +1 -1
- package/dist/parser/tests/statement/Enum.spec.js +318 -0
- package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
- package/dist/validators/ClassValidator.js +1 -1
- package/dist/validators/ClassValidator.js.map +1 -1
- package/package.json +6 -13
|
@@ -7,35 +7,129 @@ const DiagnosticMessages_1 = require("../../DiagnosticMessages");
|
|
|
7
7
|
const Parser_1 = require("../../parser/Parser");
|
|
8
8
|
const util_1 = require("../../util");
|
|
9
9
|
const reflection_1 = require("../../astUtils/reflection");
|
|
10
|
+
const visitors_1 = require("../../astUtils/visitors");
|
|
10
11
|
const TokenKind_1 = require("../../lexer/TokenKind");
|
|
11
12
|
class CodeActionsProcessor {
|
|
12
13
|
constructor(event) {
|
|
13
14
|
this.event = event;
|
|
14
15
|
this.suggestedImports = new Set();
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Processes all diagnostics in the event and emits code actions for each recognized diagnostic code.
|
|
19
|
+
*/
|
|
16
20
|
process() {
|
|
21
|
+
// First pass: individual fixes for each diagnostic at the cursor position
|
|
17
22
|
for (const diagnostic of this.event.diagnostics) {
|
|
18
23
|
if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.cannotFindName || diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.cannotFindFunction) {
|
|
19
|
-
this.
|
|
24
|
+
this.suggestCannotFindNameQuickFix(diagnostic);
|
|
20
25
|
}
|
|
21
26
|
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.classCouldNotBeFound) {
|
|
22
|
-
this.
|
|
27
|
+
this.suggestClassImportQuickFix(diagnostic);
|
|
23
28
|
}
|
|
24
29
|
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.xmlComponentMissingExtendsAttribute) {
|
|
25
|
-
this.
|
|
30
|
+
this.suggestMissingExtendsQuickFix(diagnostic);
|
|
26
31
|
}
|
|
27
32
|
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.voidFunctionMayNotReturnValue) {
|
|
28
|
-
this.
|
|
33
|
+
this.suggestVoidFunctionReturnQuickFixes([diagnostic]);
|
|
29
34
|
}
|
|
30
35
|
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.nonVoidFunctionMustReturnValue) {
|
|
31
|
-
this.
|
|
36
|
+
this.suggestNonVoidFunctionReturnQuickFixes([diagnostic]);
|
|
37
|
+
}
|
|
38
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.referencedFileDoesNotExist) {
|
|
39
|
+
this.suggestRemoveScriptImportQuickFixes([diagnostic]);
|
|
40
|
+
}
|
|
41
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryScriptImportInChildFromParent) {
|
|
42
|
+
this.suggestRemoveScriptImportQuickFixes([diagnostic]);
|
|
43
|
+
}
|
|
44
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryCodebehindScriptImport) {
|
|
45
|
+
this.suggestRemoveScriptImportQuickFixes([diagnostic]);
|
|
46
|
+
}
|
|
47
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.scriptImportCaseMismatch) {
|
|
48
|
+
this.suggestScriptImportCasingQuickFixes([diagnostic]);
|
|
49
|
+
}
|
|
50
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.missingOverrideKeyword) {
|
|
51
|
+
this.suggestMissingOverrideQuickFixes([diagnostic]);
|
|
52
|
+
}
|
|
53
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.cannotUseOverrideKeywordOnConstructorFunction) {
|
|
54
|
+
this.suggestRemoveOverrideFromConstructorQuickFixes([diagnostic]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Second pass: fix-all actions for any code that appeared in the event.
|
|
58
|
+
// Also makes sure that fix-all actions appear after individual fixes
|
|
59
|
+
const eventCodes = new Set(this.event.diagnostics.map(d => d.code));
|
|
60
|
+
const fixAllDiagsByCode = this.collectFixAllDiagnostics(eventCodes);
|
|
61
|
+
// only offer fix-all when there are multiple instances of the same issue in the file
|
|
62
|
+
for (const [code, allInFile] of fixAllDiagsByCode) {
|
|
63
|
+
if (allInFile.length > 1) {
|
|
64
|
+
if (code === DiagnosticMessages_1.DiagnosticCodeMap.voidFunctionMayNotReturnValue) {
|
|
65
|
+
this.suggestVoidFunctionReturnQuickFixes(allInFile);
|
|
66
|
+
}
|
|
67
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.nonVoidFunctionMustReturnValue) {
|
|
68
|
+
this.suggestNonVoidFunctionReturnQuickFixes(allInFile);
|
|
69
|
+
}
|
|
70
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryCodebehindScriptImport) {
|
|
71
|
+
this.suggestRemoveScriptImportQuickFixes(allInFile);
|
|
72
|
+
}
|
|
73
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.cannotUseOverrideKeywordOnConstructorFunction) {
|
|
74
|
+
this.suggestRemoveOverrideFromConstructorQuickFixes(allInFile);
|
|
75
|
+
}
|
|
76
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.referencedFileDoesNotExist) {
|
|
77
|
+
this.suggestRemoveScriptImportQuickFixes(allInFile);
|
|
78
|
+
}
|
|
79
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryScriptImportInChildFromParent) {
|
|
80
|
+
this.suggestRemoveScriptImportQuickFixes(allInFile);
|
|
81
|
+
}
|
|
82
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.scriptImportCaseMismatch) {
|
|
83
|
+
this.suggestScriptImportCasingQuickFixes(allInFile);
|
|
84
|
+
}
|
|
85
|
+
else if (code === DiagnosticMessages_1.DiagnosticCodeMap.missingOverrideKeyword) {
|
|
86
|
+
this.suggestMissingOverrideQuickFixes(allInFile);
|
|
87
|
+
}
|
|
32
88
|
}
|
|
33
89
|
}
|
|
90
|
+
// Import fix-all aggregates across multiple codes so it runs as its own step
|
|
91
|
+
if (eventCodes.has(DiagnosticMessages_1.DiagnosticCodeMap.cannotFindName) ||
|
|
92
|
+
eventCodes.has(DiagnosticMessages_1.DiagnosticCodeMap.cannotFindFunction) ||
|
|
93
|
+
eventCodes.has(DiagnosticMessages_1.DiagnosticCodeMap.classCouldNotBeFound)) {
|
|
94
|
+
this.suggestMissingImportsFixAllQuickFix();
|
|
95
|
+
}
|
|
96
|
+
this.suggestedImports.clear();
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Builds a map of diagnostic code → all matching diagnostics in the current file for each
|
|
100
|
+
* code in `eventCodes`. Scope-level codes are not present in `file.getDiagnostics()` so they
|
|
101
|
+
* are sourced from `program.getDiagnostics()` (fetched lazily, only when needed).
|
|
102
|
+
*/
|
|
103
|
+
collectFixAllDiagnostics(eventCodes) {
|
|
104
|
+
var _a;
|
|
105
|
+
const scopeLevelCodes = new Set([
|
|
106
|
+
DiagnosticMessages_1.DiagnosticCodeMap.referencedFileDoesNotExist,
|
|
107
|
+
DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryScriptImportInChildFromParent,
|
|
108
|
+
DiagnosticMessages_1.DiagnosticCodeMap.scriptImportCaseMismatch,
|
|
109
|
+
DiagnosticMessages_1.DiagnosticCodeMap.missingOverrideKeyword
|
|
110
|
+
]);
|
|
111
|
+
const fileDiagsByCode = new Map();
|
|
112
|
+
for (const d of this.event.file.getDiagnostics()) {
|
|
113
|
+
if (!fileDiagsByCode.has(d.code)) {
|
|
114
|
+
fileDiagsByCode.set(d.code, []);
|
|
115
|
+
}
|
|
116
|
+
fileDiagsByCode.get(d.code).push(d);
|
|
117
|
+
}
|
|
118
|
+
const allScopeFileDiags = [...eventCodes].some(c => scopeLevelCodes.has(c))
|
|
119
|
+
? this.event.program.getDiagnostics().filter(d => d.file === this.event.file)
|
|
120
|
+
: [];
|
|
121
|
+
const result = new Map();
|
|
122
|
+
for (const code of eventCodes) {
|
|
123
|
+
result.set(code, scopeLevelCodes.has(code)
|
|
124
|
+
? allScopeFileDiags.filter(d => d.code === code)
|
|
125
|
+
: (_a = fileDiagsByCode.get(code)) !== null && _a !== void 0 ? _a : []);
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
34
128
|
}
|
|
35
129
|
/**
|
|
36
130
|
* Generic import suggestion function. Shouldn't be called directly from the main loop, but instead called by more specific diagnostic handlers
|
|
37
131
|
*/
|
|
38
|
-
|
|
132
|
+
suggestImportQuickFix(diagnostic, key, files) {
|
|
39
133
|
var _a, _b, _c;
|
|
40
134
|
//skip if we already have this suggestion
|
|
41
135
|
if (this.suggestedImports.has(key)) {
|
|
@@ -62,29 +156,102 @@ class CodeActionsProcessor {
|
|
|
62
156
|
}));
|
|
63
157
|
}
|
|
64
158
|
}
|
|
65
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Suggests import statements for an unresolved name (function, class, namespace, or enum).
|
|
161
|
+
*/
|
|
162
|
+
suggestCannotFindNameQuickFix(diagnostic) {
|
|
66
163
|
var _a;
|
|
67
164
|
//skip if not a BrighterScript file
|
|
68
165
|
if (diagnostic.file.parseMode !== Parser_1.ParseMode.BrighterScript) {
|
|
69
166
|
return;
|
|
70
167
|
}
|
|
71
168
|
const lowerName = ((_a = diagnostic.data.fullName) !== null && _a !== void 0 ? _a : diagnostic.data.name).toLowerCase();
|
|
72
|
-
this.
|
|
169
|
+
this.suggestImportQuickFix(diagnostic, lowerName, [
|
|
73
170
|
...this.event.file.program.findFilesForFunction(lowerName),
|
|
74
171
|
...this.event.file.program.findFilesForClass(lowerName),
|
|
75
172
|
...this.event.file.program.findFilesForNamespace(lowerName),
|
|
76
173
|
...this.event.file.program.findFilesForEnum(lowerName)
|
|
77
174
|
]);
|
|
78
175
|
}
|
|
79
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Suggests import statements for an unresolved class name.
|
|
178
|
+
*/
|
|
179
|
+
suggestClassImportQuickFix(diagnostic) {
|
|
80
180
|
//skip if not a BrighterScript file
|
|
81
181
|
if (diagnostic.file.parseMode !== Parser_1.ParseMode.BrighterScript) {
|
|
82
182
|
return;
|
|
83
183
|
}
|
|
84
184
|
const lowerClassName = diagnostic.data.className.toLowerCase();
|
|
85
|
-
this.
|
|
185
|
+
this.suggestImportQuickFix(diagnostic, lowerClassName, this.event.file.program.findFilesForClass(lowerClassName));
|
|
86
186
|
}
|
|
87
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Scans all import-related diagnostics in the file and emits a single composite
|
|
189
|
+
* "Fix all: Add missing imports" action when 2+ unambiguous imports are needed.
|
|
190
|
+
* Ambiguous names (multiple possible source files) are excluded since we cannot
|
|
191
|
+
* automatically choose one.
|
|
192
|
+
*/
|
|
193
|
+
suggestMissingImportsFixAllQuickFix() {
|
|
194
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
195
|
+
if (!(0, reflection_1.isBrsFile)(this.event.file) || this.event.file.parseMode !== Parser_1.ParseMode.BrighterScript) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const file = this.event.file;
|
|
199
|
+
const importStatements = file.parser.references.importStatements;
|
|
200
|
+
const insertPosition = (_c = (_b = (_a = importStatements[importStatements.length - 1]) === null || _a === void 0 ? void 0 : _a.importToken.range) === null || _b === void 0 ? void 0 : _b.start) !== null && _c !== void 0 ? _c : util_1.util.createPosition(0, 0);
|
|
201
|
+
const changes = [];
|
|
202
|
+
const addedPaths = new Set();
|
|
203
|
+
// cannotFindName/classCouldNotBeFound are scope-level diagnostics, so we must
|
|
204
|
+
// use program.getDiagnostics() (filtered by file) rather than file.getDiagnostics().
|
|
205
|
+
const allFileDiagnostics = this.event.program.getDiagnostics().filter(d => d.file === file);
|
|
206
|
+
for (const diagnostic of allFileDiagnostics) {
|
|
207
|
+
let files = [];
|
|
208
|
+
if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.cannotFindName || diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.cannotFindFunction) {
|
|
209
|
+
const cannotFindNameDiagnostic = diagnostic;
|
|
210
|
+
const lowerName = (_g = ((_e = (_d = cannotFindNameDiagnostic.data) === null || _d === void 0 ? void 0 : _d.fullName) !== null && _e !== void 0 ? _e : (_f = cannotFindNameDiagnostic.data) === null || _f === void 0 ? void 0 : _f.name)) === null || _g === void 0 ? void 0 : _g.toLowerCase();
|
|
211
|
+
if (lowerName) {
|
|
212
|
+
files = [
|
|
213
|
+
...file.program.findFilesForFunction(lowerName),
|
|
214
|
+
...file.program.findFilesForClass(lowerName),
|
|
215
|
+
...file.program.findFilesForNamespace(lowerName),
|
|
216
|
+
...file.program.findFilesForEnum(lowerName)
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.classCouldNotBeFound) {
|
|
221
|
+
const classCouldNotBeFoundDiagnostic = diagnostic;
|
|
222
|
+
const lowerClassName = (_j = (_h = classCouldNotBeFoundDiagnostic.data) === null || _h === void 0 ? void 0 : _h.className) === null || _j === void 0 ? void 0 : _j.toLowerCase();
|
|
223
|
+
if (lowerClassName) {
|
|
224
|
+
files = file.program.findFilesForClass(lowerClassName);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
//skip ambiguous names — we can't choose a file automatically
|
|
228
|
+
if (files.length !== 1) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const pkgPath = util_1.util.getRokuPkgPath(files[0].pkgPath);
|
|
232
|
+
if (!addedPaths.has(pkgPath)) {
|
|
233
|
+
addedPaths.add(pkgPath);
|
|
234
|
+
changes.push({
|
|
235
|
+
type: 'insert',
|
|
236
|
+
filePath: file.srcPath,
|
|
237
|
+
position: insertPosition,
|
|
238
|
+
newText: `import "${pkgPath}"\n`
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (changes.length > 1) {
|
|
243
|
+
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
244
|
+
title: `Fix all: Auto fixable missing imports`,
|
|
245
|
+
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
246
|
+
changes: changes
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Adds code actions to insert a missing `extends` attribute on an XML component tag.
|
|
252
|
+
* Offers Group, Task, and ContentNode as common choices.
|
|
253
|
+
*/
|
|
254
|
+
suggestMissingExtendsQuickFix(diagnostic) {
|
|
88
255
|
var _a;
|
|
89
256
|
const srcPath = this.event.file.srcPath;
|
|
90
257
|
const { component } = this.event.file.parser.ast;
|
|
@@ -125,19 +292,17 @@ class CodeActionsProcessor {
|
|
|
125
292
|
}]
|
|
126
293
|
}));
|
|
127
294
|
}
|
|
128
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Adds code actions to resolve a `voidFunctionMayNotReturnValue` diagnostic.
|
|
297
|
+
* Offers removing the return value, converting sub→function, or removing an `as void` return type.
|
|
298
|
+
*/
|
|
299
|
+
suggestVoidFunctionReturnQuickFixes(diagnostics) {
|
|
129
300
|
var _a, _b, _c, _d;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type: 'delete',
|
|
136
|
-
filePath: this.event.file.srcPath,
|
|
137
|
-
range: util_1.util.createRange(diagnostic.range.start.line, diagnostic.range.start.character + 'return'.length, diagnostic.range.end.line, diagnostic.range.end.character)
|
|
138
|
-
}]
|
|
139
|
-
}));
|
|
140
|
-
if ((0, reflection_1.isBrsFile)(this.event.file)) {
|
|
301
|
+
const changes = diagnostics.map(d => this.getRemoveReturnValueChange(d));
|
|
302
|
+
this.emitOrFixAll(`Remove return value`, `Fix all: Remove void return values`, changes, diagnostics[0]);
|
|
303
|
+
//contextual BrsFile actions only apply to the individual (single-violation) case
|
|
304
|
+
if (changes.length === 1 && (0, reflection_1.isBrsFile)(this.event.file)) {
|
|
305
|
+
const diagnostic = diagnostics[0];
|
|
141
306
|
const expression = this.event.file.getClosestExpression(diagnostic.range.start);
|
|
142
307
|
const func = expression.findAncestor(reflection_1.isFunctionExpression);
|
|
143
308
|
//if we're in a sub and we do not have a return type, suggest converting to a function
|
|
@@ -154,19 +319,9 @@ class CodeActionsProcessor {
|
|
|
154
319
|
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
155
320
|
changes: [
|
|
156
321
|
//function
|
|
157
|
-
{
|
|
158
|
-
type: 'replace',
|
|
159
|
-
filePath: this.event.file.srcPath,
|
|
160
|
-
range: func.functionType.range,
|
|
161
|
-
newText: functionTypeText
|
|
162
|
-
},
|
|
322
|
+
{ type: 'replace', filePath: this.event.file.srcPath, range: func.functionType.range, newText: functionTypeText },
|
|
163
323
|
//end function
|
|
164
|
-
{
|
|
165
|
-
type: 'replace',
|
|
166
|
-
filePath: this.event.file.srcPath,
|
|
167
|
-
range: func.end.range,
|
|
168
|
-
newText: endFunctionTypeText
|
|
169
|
-
}
|
|
324
|
+
{ type: 'replace', filePath: this.event.file.srcPath, range: func.end.range, newText: endFunctionTypeText }
|
|
170
325
|
]
|
|
171
326
|
}));
|
|
172
327
|
}
|
|
@@ -176,90 +331,217 @@ class CodeActionsProcessor {
|
|
|
176
331
|
title: `Remove return type from function declaration`,
|
|
177
332
|
diagnostics: [diagnostic],
|
|
178
333
|
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
179
|
-
changes: [
|
|
180
|
-
type: 'delete',
|
|
181
|
-
filePath: this.event.file.srcPath,
|
|
182
|
-
// )| as void|
|
|
183
|
-
range: util_1.util.createRange(func.rightParen.range.start.line, func.rightParen.range.start.character + 1, func.returnTypeToken.range.end.line, func.returnTypeToken.range.end.character)
|
|
184
|
-
}]
|
|
334
|
+
changes: [this.getRemoveFunctionReturnTypeChange(func)]
|
|
185
335
|
}));
|
|
186
336
|
}
|
|
187
337
|
}
|
|
188
338
|
}
|
|
189
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Adds code actions to resolve a `nonVoidFunctionMustReturnValue` diagnostic.
|
|
341
|
+
* Offers removing the return type from a sub, adding `as void` to a function, or converting function→sub.
|
|
342
|
+
*/
|
|
343
|
+
suggestNonVoidFunctionReturnQuickFixes(diagnostics) {
|
|
344
|
+
if (!(0, reflection_1.isBrsFile)(this.event.file)) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const file = this.event.file;
|
|
348
|
+
//find tokens for `as`, `void`, `sub`, `end sub` in the file if possible
|
|
349
|
+
let asText;
|
|
350
|
+
let voidText;
|
|
351
|
+
let subText;
|
|
352
|
+
let endSubText;
|
|
353
|
+
for (const token of file.parser.tokens) {
|
|
354
|
+
if (asText && voidText && subText && endSubText) {
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
if ((token === null || token === void 0 ? void 0 : token.kind) === TokenKind_1.TokenKind.As) {
|
|
358
|
+
asText = token === null || token === void 0 ? void 0 : token.text;
|
|
359
|
+
}
|
|
360
|
+
else if ((token === null || token === void 0 ? void 0 : token.kind) === TokenKind_1.TokenKind.Void) {
|
|
361
|
+
voidText = token === null || token === void 0 ? void 0 : token.text;
|
|
362
|
+
}
|
|
363
|
+
else if ((token === null || token === void 0 ? void 0 : token.kind) === TokenKind_1.TokenKind.Sub) {
|
|
364
|
+
subText = token === null || token === void 0 ? void 0 : token.text;
|
|
365
|
+
}
|
|
366
|
+
else if ((token === null || token === void 0 ? void 0 : token.kind) === TokenKind_1.TokenKind.EndSub) {
|
|
367
|
+
endSubText = token === null || token === void 0 ? void 0 : token.text;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Build per-fix-type change arrays, deduplicating by enclosing function so that one
|
|
371
|
+
// function with multiple bare returns only contributes one change.
|
|
372
|
+
const removeReturnTypeChanges = [];
|
|
373
|
+
const addVoidChanges = [];
|
|
374
|
+
const seenFunctions = new Set();
|
|
375
|
+
for (const d of diagnostics) {
|
|
376
|
+
const expr = file.getClosestExpression(d.range.start);
|
|
377
|
+
const fn = expr === null || expr === void 0 ? void 0 : expr.findAncestor(reflection_1.isFunctionExpression);
|
|
378
|
+
if (!fn) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const fnKey = `${fn.range.start.line}:${fn.range.start.character}`;
|
|
382
|
+
if (seenFunctions.has(fnKey)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
seenFunctions.add(fnKey);
|
|
386
|
+
if (fn.functionType.kind === TokenKind_1.TokenKind.Sub && fn.returnTypeToken && fn.returnTypeToken.kind !== TokenKind_1.TokenKind.Void) {
|
|
387
|
+
removeReturnTypeChanges.push(this.getRemoveFunctionReturnTypeChange(fn));
|
|
388
|
+
}
|
|
389
|
+
else if (fn.functionType.kind === TokenKind_1.TokenKind.Function && !fn.returnTypeToken) {
|
|
390
|
+
addVoidChanges.push({
|
|
391
|
+
type: 'insert',
|
|
392
|
+
filePath: this.event.file.srcPath,
|
|
393
|
+
position: fn.rightParen.range.end,
|
|
394
|
+
newText: ` ${asText !== null && asText !== void 0 ? asText : 'as'} ${voidText !== null && voidText !== void 0 ? voidText : 'void'}`
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
this.emitOrFixAll(`Remove return type from sub declaration`, `Fix all: Remove return type from sub declarations`, removeReturnTypeChanges, diagnostics[0]);
|
|
399
|
+
this.emitOrFixAll(`Add void return type to function declaration`, `Fix all: Add void return type to function declarations`, addVoidChanges, diagnostics[0]);
|
|
400
|
+
//'Convert function to sub' has no fix-all variant; only add it for the individual case
|
|
401
|
+
if (addVoidChanges.length === 1 && diagnostics.length === 1) {
|
|
402
|
+
const func = file.getClosestExpression(diagnostics[0].range.start).findAncestor(reflection_1.isFunctionExpression);
|
|
403
|
+
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
404
|
+
title: `Convert function to sub`,
|
|
405
|
+
diagnostics: [diagnostics[0]],
|
|
406
|
+
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
407
|
+
changes: [
|
|
408
|
+
{ type: 'replace', filePath: file.srcPath, range: func.functionType.range, newText: subText !== null && subText !== void 0 ? subText : 'sub' },
|
|
409
|
+
{ type: 'replace', filePath: file.srcPath, range: func.end.range, newText: endSubText !== null && endSubText !== void 0 ? endSubText : 'end sub' }
|
|
410
|
+
]
|
|
411
|
+
}));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// ---- script import fixes ----
|
|
415
|
+
/**
|
|
416
|
+
* Adds code actions to delete one or more unnecessary or broken script import lines.
|
|
417
|
+
*/
|
|
418
|
+
suggestRemoveScriptImportQuickFixes(diagnostics) {
|
|
419
|
+
var _a, _b;
|
|
420
|
+
const titles = {
|
|
421
|
+
[DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryScriptImportInChildFromParent]: ['Remove redundant script import', 'Fix all: Remove redundant script imports'],
|
|
422
|
+
[DiagnosticMessages_1.DiagnosticCodeMap.unnecessaryCodebehindScriptImport]: ['Remove unnecessary codebehind import', 'Fix all: Remove unnecessary codebehind imports']
|
|
423
|
+
};
|
|
424
|
+
const [singleTitle, fixAllTitle] = (_b = titles[(_a = diagnostics[0]) === null || _a === void 0 ? void 0 : _a.code]) !== null && _b !== void 0 ? _b : ['Remove script import', 'Fix all: Remove script imports'];
|
|
425
|
+
const changes = diagnostics.map(diagnostic => {
|
|
426
|
+
return {
|
|
427
|
+
type: 'delete',
|
|
428
|
+
filePath: this.event.file.srcPath,
|
|
429
|
+
range: util_1.util.createRange(diagnostic.range.start.line, 0, diagnostic.range.start.line + 1, 0)
|
|
430
|
+
};
|
|
431
|
+
});
|
|
432
|
+
this.emitOrFixAll(singleTitle, fixAllTitle, changes, diagnostics[0]);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Adds code actions to correct the casing of script import paths to match the actual file name on disk.
|
|
436
|
+
*/
|
|
437
|
+
suggestScriptImportCasingQuickFixes(diagnostics) {
|
|
190
438
|
var _a;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
197
|
-
title: `Remove return type from sub declaration`,
|
|
198
|
-
diagnostics: [diagnostic],
|
|
199
|
-
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
200
|
-
changes: [{
|
|
201
|
-
type: 'delete',
|
|
202
|
-
filePath: this.event.file.srcPath,
|
|
203
|
-
// )| as void|
|
|
204
|
-
range: util_1.util.createRange(func.rightParen.range.start.line, func.rightParen.range.start.character + 1, func.returnTypeToken.range.end.line, func.returnTypeToken.range.end.character)
|
|
205
|
-
}]
|
|
206
|
-
}));
|
|
439
|
+
const changes = [];
|
|
440
|
+
for (const diagnostic of diagnostics) {
|
|
441
|
+
const correctFilePath = (_a = diagnostic.data) === null || _a === void 0 ? void 0 : _a.correctFilePath;
|
|
442
|
+
if (!correctFilePath) {
|
|
443
|
+
continue;
|
|
207
444
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
445
|
+
changes.push({
|
|
446
|
+
type: 'replace',
|
|
447
|
+
filePath: this.event.file.srcPath,
|
|
448
|
+
range: diagnostic.range,
|
|
449
|
+
newText: correctFilePath
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
this.emitOrFixAll('Fix script import path casing', 'Fix all: Fix script import path casing', changes, diagnostics[0]);
|
|
453
|
+
}
|
|
454
|
+
// ---- override keyword fixes ----
|
|
455
|
+
/**
|
|
456
|
+
* Adds code actions to insert the missing `override` keyword before a method declaration.
|
|
457
|
+
*/
|
|
458
|
+
suggestMissingOverrideQuickFixes(diagnostics) {
|
|
459
|
+
if (!(0, reflection_1.isBrsFile)(this.event.file)) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const file = this.event.file;
|
|
463
|
+
const changes = [];
|
|
464
|
+
for (const diagnostic of diagnostics) {
|
|
465
|
+
let insertPosition;
|
|
466
|
+
file.ast.walk((node) => {
|
|
467
|
+
var _a, _b, _c, _d, _e, _f;
|
|
468
|
+
if ((0, reflection_1.isMethodStatement)(node) &&
|
|
469
|
+
((_b = (_a = node.range) === null || _a === void 0 ? void 0 : _a.start) === null || _b === void 0 ? void 0 : _b.line) === diagnostic.range.start.line &&
|
|
470
|
+
((_d = (_c = node.range) === null || _c === void 0 ? void 0 : _c.start) === null || _d === void 0 ? void 0 : _d.character) === diagnostic.range.start.character) {
|
|
471
|
+
insertPosition = (_f = (_e = node.func.functionType) === null || _e === void 0 ? void 0 : _e.range) === null || _f === void 0 ? void 0 : _f.start;
|
|
231
472
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
position: func.rightParen.range.end,
|
|
241
|
-
newText: ` ${asText !== null && asText !== void 0 ? asText : 'as'} ${voidText !== null && voidText !== void 0 ? voidText : 'void'}`
|
|
242
|
-
}]
|
|
243
|
-
}));
|
|
244
|
-
//suggest converting to sub
|
|
245
|
-
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
246
|
-
title: `Convert function to sub`,
|
|
247
|
-
diagnostics: [diagnostic],
|
|
248
|
-
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
249
|
-
changes: [{
|
|
250
|
-
type: 'replace',
|
|
251
|
-
filePath: this.event.file.srcPath,
|
|
252
|
-
range: func.functionType.range,
|
|
253
|
-
newText: subText !== null && subText !== void 0 ? subText : 'sub'
|
|
254
|
-
}, {
|
|
255
|
-
type: 'replace',
|
|
256
|
-
filePath: this.event.file.srcPath,
|
|
257
|
-
range: func.end.range,
|
|
258
|
-
newText: endSubText !== null && endSubText !== void 0 ? endSubText : 'end sub'
|
|
259
|
-
}]
|
|
260
|
-
}));
|
|
473
|
+
}, { walkMode: visitors_1.WalkMode.visitStatementsRecursive });
|
|
474
|
+
if (insertPosition) {
|
|
475
|
+
changes.push({
|
|
476
|
+
type: 'insert',
|
|
477
|
+
filePath: file.srcPath,
|
|
478
|
+
position: insertPosition,
|
|
479
|
+
newText: 'override '
|
|
480
|
+
});
|
|
261
481
|
}
|
|
262
482
|
}
|
|
483
|
+
this.emitOrFixAll(`Add missing 'override' keyword`, `Fix all: Add missing 'override' keywords`, changes, diagnostics[0]);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Adds code actions to remove the invalid `override` keyword from a constructor method.
|
|
487
|
+
*/
|
|
488
|
+
suggestRemoveOverrideFromConstructorQuickFixes(diagnostics) {
|
|
489
|
+
const changes = diagnostics.map(d => ({
|
|
490
|
+
type: 'delete',
|
|
491
|
+
filePath: this.event.file.srcPath,
|
|
492
|
+
// delete "override " — the keyword token plus the trailing space before function/sub
|
|
493
|
+
range: util_1.util.createRange(d.range.start.line, d.range.start.character, d.range.end.line, d.range.end.character + 1)
|
|
494
|
+
}));
|
|
495
|
+
this.emitOrFixAll(`Remove 'override' from constructor`, `Fix all: Remove 'override' from constructors`, changes, diagnostics[0]);
|
|
496
|
+
}
|
|
497
|
+
// ---- change helpers ----
|
|
498
|
+
/**
|
|
499
|
+
* Builds a delete change that removes the return value from a `return <expr>` statement,
|
|
500
|
+
* leaving just a bare `return`.
|
|
501
|
+
*/
|
|
502
|
+
getRemoveReturnValueChange(diagnostic) {
|
|
503
|
+
return {
|
|
504
|
+
type: 'delete',
|
|
505
|
+
filePath: this.event.file.srcPath,
|
|
506
|
+
range: util_1.util.createRange(diagnostic.range.start.line, diagnostic.range.start.character + 'return'.length, diagnostic.range.end.line, diagnostic.range.end.character)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Builds the change that deletes `) as <type>` from a function/sub declaration.
|
|
511
|
+
* Used for both `as void` on a function and any return type on a sub.
|
|
512
|
+
*/
|
|
513
|
+
getRemoveFunctionReturnTypeChange(func) {
|
|
514
|
+
return {
|
|
515
|
+
type: 'delete',
|
|
516
|
+
filePath: this.event.file.srcPath,
|
|
517
|
+
// )| as <type>|
|
|
518
|
+
range: util_1.util.createRange(func.rightParen.range.start.line, func.rightParen.range.start.character + 1, func.returnTypeToken.range.end.line, func.returnTypeToken.range.end.character)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Emits a single code action when there is exactly one change, or a "fix all" composite
|
|
523
|
+
* action when there are multiple changes (same pattern as ESLint's "Fix all X problems").
|
|
524
|
+
* Does nothing when the changes array is empty.
|
|
525
|
+
*/
|
|
526
|
+
emitOrFixAll(singleTitle, fixAllTitle, changes, diagnostic) {
|
|
527
|
+
if (changes.length === 0) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (changes.length === 1) {
|
|
531
|
+
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
532
|
+
title: singleTitle,
|
|
533
|
+
diagnostics: [diagnostic],
|
|
534
|
+
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
535
|
+
changes: changes
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({
|
|
540
|
+
title: fixAllTitle,
|
|
541
|
+
kind: vscode_languageserver_1.CodeActionKind.QuickFix,
|
|
542
|
+
changes: changes
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
263
545
|
}
|
|
264
546
|
}
|
|
265
547
|
exports.CodeActionsProcessor = CodeActionsProcessor;
|