brighterscript
Advanced tools
Comparing version 0.35.0 to 0.36.0
@@ -9,2 +9,15 @@ # Changelog | ||
## [0.36.0] - 2021-03-15 | ||
### Added | ||
- class import code actions ([#365](https://github.com/rokucommunity/brighterscript/pull/365)) | ||
### Changed | ||
- append stack trace to every language server error ([#354)](https://github.com/rokucommunity/brighterscript/pull/354)) | ||
### Fixed | ||
- restrict function and class imports to .bs files only ([#365)](https://github.com/rokucommunity/brighterscript/pull/365)) | ||
- language server crashes due to unsafe property access in callfunc expressions ([#360)](https://github.com/rokucommunity/brighterscript/pull/360)) | ||
- crashes in signature help ([#358)](https://github.com/rokucommunity/brighterscript/pull/358)) | ||
- template string transpile bug when two expressions were next to each other ([#361)](https://github.com/rokucommunity/brighterscript/pull/361)) | ||
## [0.35.0] - 2021-03-09 | ||
@@ -1044,1 +1057,2 @@ ### Added | ||
[0.35.0]: https://github.com/rokucommunity/brighterscript/compare/v0.34.3...v0.35.0 | ||
[0.36.0]: https://github.com/rokucommunity/brighterscript/compare/v0.35.0...v0.36.0 |
@@ -7,4 +7,9 @@ import type { OnGetCodeActionsEvent } from '../../interfaces'; | ||
private suggestedImports; | ||
/** | ||
* Generic import suggestion function. Shouldn't be called directly from the main loop, but instead called by more specific diagnostic handlers | ||
*/ | ||
private suggestImports; | ||
private suggestFunctionImports; | ||
private suggestClassImports; | ||
private addMissingExtends; | ||
} |
@@ -7,2 +7,3 @@ "use strict"; | ||
const DiagnosticMessages_1 = require("../../DiagnosticMessages"); | ||
const parser_1 = require("../../parser"); | ||
const util_1 = require("../../util"); | ||
@@ -17,4 +18,7 @@ class CodeActionsProcessor { | ||
if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.callToUnknownFunction) { | ||
this.suggestImports(diagnostic); | ||
this.suggestFunctionImports(diagnostic); | ||
} | ||
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.classCouldNotBeFound) { | ||
this.suggestClassImports(diagnostic); | ||
} | ||
else if (diagnostic.code === DiagnosticMessages_1.DiagnosticCodeMap.xmlComponentMissingExtendsAttribute) { | ||
@@ -25,10 +29,12 @@ this.addMissingExtends(diagnostic); | ||
} | ||
suggestImports(diagnostic) { | ||
/** | ||
* Generic import suggestion function. Shouldn't be called directly from the main loop, but instead called by more specific diagnostic handlers | ||
*/ | ||
suggestImports(diagnostic, key, files) { | ||
var _a, _b, _c; | ||
const lowerFunctionName = diagnostic.data.functionName.toLowerCase(); | ||
//skip generating duplicate suggestions if we've already done this one | ||
if (this.suggestedImports.has(lowerFunctionName)) { | ||
//skip if we already have this suggestion | ||
if (this.suggestedImports.has(key)) { | ||
return; | ||
} | ||
this.suggestedImports.add(lowerFunctionName); | ||
this.suggestedImports.add(key); | ||
const importStatements = this.event.file.parser.references.importStatements; | ||
@@ -38,3 +44,3 @@ //find the position of the first import statement, or the top of the file if there is none | ||
//find all files that reference this function | ||
for (const file of this.event.file.program.findFilesForFunction(lowerFunctionName)) { | ||
for (const file of files) { | ||
const pkgPath = util_1.util.getRokuPkgPath(file.pkgPath); | ||
@@ -55,2 +61,18 @@ this.event.codeActions.push(CodeActionUtil_1.codeActionUtil.createCodeAction({ | ||
} | ||
suggestFunctionImports(diagnostic) { | ||
//skip if not a BrighterScript file | ||
if (diagnostic.file.parseMode !== parser_1.ParseMode.BrighterScript) { | ||
return; | ||
} | ||
const lowerFunctionName = diagnostic.data.functionName.toLowerCase(); | ||
this.suggestImports(diagnostic, lowerFunctionName, this.event.file.program.findFilesForFunction(lowerFunctionName)); | ||
} | ||
suggestClassImports(diagnostic) { | ||
//skip if not a BrighterScript file | ||
if (diagnostic.file.parseMode !== parser_1.ParseMode.BrighterScript) { | ||
return; | ||
} | ||
const lowerClassName = diagnostic.data.className.toLowerCase(); | ||
this.suggestImports(diagnostic, lowerClassName, this.event.file.program.findFilesForClass(lowerClassName)); | ||
} | ||
addMissingExtends(diagnostic) { | ||
@@ -57,0 +79,0 @@ var _a; |
@@ -74,43 +74,120 @@ "use strict"; | ||
}); | ||
it('does not produce duplicate code actions for bs imports', () => { | ||
//define the function in two files | ||
program.addOrReplaceFile('components/lib1.brs', ` | ||
sub doSomething() | ||
end sub | ||
`); | ||
program.addOrReplaceFile('components/lib2.brs', ` | ||
sub doSomething() | ||
end sub | ||
`); | ||
//use the function in this file | ||
const componentCommonFile = program.addOrReplaceFile('components/ComponentCommon.bs', ` | ||
sub init() | ||
doSomething() | ||
end sub | ||
`); | ||
//import the file in two scopes | ||
program.addOrReplaceFile('components/comp1.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="ComponentCommon.bs" /> | ||
</component> | ||
`); | ||
program.addOrReplaceFile('components/comp2.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="ComponentCommon.bs" /> | ||
</component> | ||
`); | ||
program.validate(); | ||
//we should only get each file import suggestion exactly once | ||
const codeActions = program.getCodeActions(componentCommonFile.pathAbsolute, | ||
// doSome|thing() | ||
util_1.util.createRange(2, 22, 2, 22)); | ||
chai_1.expect(codeActions.map(x => x.title).sort()).to.eql([ | ||
`import "pkg:/components/lib1.brs"`, | ||
`import "pkg:/components/lib2.brs"` | ||
]); | ||
}); | ||
it('does not suggest imports for brs files', () => { | ||
//import the file in two scopes | ||
program.addOrReplaceFile('components/comp1.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="comp1.brs" /> | ||
</component> | ||
`); | ||
//import the function here | ||
const file = program.addOrReplaceFile('components/comp1.brs', ` | ||
sub init() | ||
DoSomething() | ||
end sub | ||
`); | ||
//define the function here | ||
program.addOrReplaceFile('source/lib.brs', ` | ||
sub DoSomething() | ||
end sub | ||
`); | ||
program.validate(); | ||
//there should be no code actions since this is a brs file | ||
const codeActions = program.getCodeActions(file.pathAbsolute, | ||
// DoSometh|ing() | ||
util_1.util.createRange(2, 28, 2, 28)); | ||
chai_1.expect(codeActions).to.be.empty; | ||
}); | ||
it('suggests class imports', () => { | ||
//import the file in two scopes | ||
program.addOrReplaceFile('components/comp1.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="comp1.bs" /> | ||
</component> | ||
`); | ||
const file = program.addOrReplaceFile('components/comp1.bs', ` | ||
sub init() | ||
dude = new Person() | ||
end sub | ||
`); | ||
program.addOrReplaceFile('source/Person.bs', ` | ||
class Person | ||
end class | ||
`); | ||
program.validate(); | ||
chai_1.expect(program.getCodeActions(file.pathAbsolute, | ||
// new Per|son() | ||
util_1.util.createRange(2, 34, 2, 34)).map(x => x.title).sort()).to.eql([ | ||
`import "pkg:/source/Person.bs"` | ||
]); | ||
}); | ||
it('suggests class imports', () => { | ||
//import the file in two scopes | ||
program.addOrReplaceFile('components/comp1.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="comp1.bs" /> | ||
</component> | ||
`); | ||
//import the function here | ||
const file = program.addOrReplaceFile('components/comp1.bs', ` | ||
sub init() | ||
kitty = new Animals.Cat() | ||
end sub | ||
`); | ||
program.addOrReplaceFile('source/Animals.bs', ` | ||
namespace Animals | ||
class Cat | ||
end class | ||
end namespace | ||
`); | ||
program.validate(); | ||
chai_1.expect(program.getCodeActions(file.pathAbsolute, | ||
// new Anim|als.Cat() | ||
util_1.util.createRange(2, 36, 2, 36)).map(x => x.title).sort()).to.eql([ | ||
`import "pkg:/source/Animals.bs"` | ||
]); | ||
}); | ||
}); | ||
it('does not produce duplicate code actions for bs imports', () => { | ||
//define the function in two files | ||
program.addOrReplaceFile('components/lib1.brs', ` | ||
sub doSomething() | ||
end sub | ||
`); | ||
program.addOrReplaceFile('components/lib2.brs', ` | ||
sub doSomething() | ||
end sub | ||
`); | ||
//use the function in this file | ||
const componentCommonFile = program.addOrReplaceFile('components/ComponentCommon.bs', ` | ||
sub init() | ||
doSomething() | ||
end sub | ||
`); | ||
//import the file in two scopes | ||
program.addOrReplaceFile('components/comp1.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="ComponentCommon.bs" /> | ||
</component> | ||
`); | ||
program.addOrReplaceFile('components/comp2.xml', testHelpers_spec_1.trim ` | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<component name="ChildScene"> | ||
<script uri="ComponentCommon.bs" /> | ||
</component> | ||
`); | ||
program.validate(); | ||
//we should only get each file import suggestion exactly once | ||
const codeActions = program.getCodeActions(componentCommonFile.pathAbsolute, | ||
// doSome|thing() | ||
util_1.util.createRange(2, 22, 2, 22)); | ||
chai_1.expect(codeActions.map(x => x.title).sort()).to.eql([ | ||
`import "pkg:/components/lib1.brs"`, | ||
`import "pkg:/components/lib2.brs"` | ||
]); | ||
}); | ||
}); | ||
//# sourceMappingURL=CodeActionsProcessor.spec.js.map |
@@ -161,2 +161,5 @@ import type { Position } from 'vscode-languageserver'; | ||
severity: 1; | ||
data: { | ||
className: string; | ||
}; | ||
}; | ||
@@ -163,0 +166,0 @@ expectedClassFieldIdentifier: () => { |
@@ -162,3 +162,6 @@ "use strict"; | ||
code: 1029, | ||
severity: vscode_languageserver_1.DiagnosticSeverity.Error | ||
severity: vscode_languageserver_1.DiagnosticSeverity.Error, | ||
data: { | ||
className: className | ||
} | ||
}), | ||
@@ -165,0 +168,0 @@ expectedClassFieldIdentifier: () => ({ |
"use strict"; | ||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -757,3 +763,2 @@ exports.CustomCommands = exports.LanguageServer = void 0; | ||
catch (e) { | ||
this.connection.tracer.log(e); | ||
this.sendCriticalFailure(`Critical error parsing/ validating ${filePath}: ${e.message}`); | ||
@@ -811,3 +816,3 @@ } | ||
async onSignatureHelp(params) { | ||
var _a, _b; | ||
var _a, _b, _c; | ||
await this.waitAllProgramFirstRuns(); | ||
@@ -828,3 +833,3 @@ const filepath = util_1.util.uriToPath(params.textDocument.uri); | ||
catch (e) { | ||
this.connection.console.error(`error in onSignatureHelp: ${e.message}${(_b = e.stack) !== null && _b !== void 0 ? _b : ''}`); | ||
this.connection.console.error(`error in onSignatureHelp: ${(_c = (_b = e.stack) !== null && _b !== void 0 ? _b : e.message) !== null && _c !== void 0 ? _c : e}`); | ||
return { | ||
@@ -880,2 +885,50 @@ signatures: [], | ||
} | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onInitialize", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onInitialized", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onCompletion", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onCompletionResolve", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onCodeAction", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onDidChangeConfiguration", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onDidChangeWatchedFiles", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onHover", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onDocumentClose", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "validateTextDocument", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onWorkspaceSymbol", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onDocumentSymbol", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onDefinition", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onSignatureHelp", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onReferences", null); | ||
__decorate([ | ||
AddStackToErrorMessage | ||
], LanguageServer.prototype, "onExecuteCommand", null); | ||
exports.LanguageServer = LanguageServer; | ||
@@ -886,2 +939,33 @@ var CustomCommands; | ||
})(CustomCommands = exports.CustomCommands || (exports.CustomCommands = {})); | ||
/** | ||
* Wraps a method. If there's an error (either sync or via a promise), | ||
* this appends the error's stack trace at the end of the error message so that the connection will | ||
*/ | ||
function AddStackToErrorMessage(target, propertyKey, descriptor) { | ||
let originalMethod = descriptor.value; | ||
//wrapping the original method | ||
descriptor.value = function value(...args) { | ||
try { | ||
let result = originalMethod.apply(this, args); | ||
//if the result looks like a promise, log if there's a rejection | ||
if (result === null || result === void 0 ? void 0 : result.then) { | ||
return Promise.resolve(result).catch((e) => { | ||
if (e === null || e === void 0 ? void 0 : e.stack) { | ||
e.message = e.stack; | ||
} | ||
return Promise.reject(e); | ||
}); | ||
} | ||
else { | ||
return result; | ||
} | ||
} | ||
catch (e) { | ||
if (e === null || e === void 0 ? void 0 : e.stack) { | ||
e.message = e.stack; | ||
} | ||
throw e; | ||
} | ||
}; | ||
} | ||
//# sourceMappingURL=LanguageServer.js.map |
@@ -851,3 +851,6 @@ "use strict"; | ||
} | ||
plus = items.length > 0 ? ' + ' : ''; | ||
//set the plus after the first occurance of a nonzero length set of items | ||
if (plus === '' && items.length > 0) { | ||
plus = ' + '; | ||
} | ||
} | ||
@@ -854,0 +857,0 @@ for (let i = 0; i < this.quasis.length; i++) { |
@@ -267,2 +267,4 @@ import type { Token } from '../lexer'; | ||
classStatements: ClassStatement[]; | ||
get classStatementLookup(): Map<string, ClassStatement>; | ||
private _classStatementLookup; | ||
functionExpressions: FunctionExpression[]; | ||
@@ -269,0 +271,0 @@ functionStatements: FunctionStatement[]; |
@@ -143,2 +143,5 @@ "use strict"; | ||
}); | ||
it('properly transpiles two template strings side-by-side', () => { | ||
testTranspile('a = `${"hello"}${"world"}`', 'a = "hello" + "world"'); | ||
}); | ||
it('skips calling toString on strings', () => { | ||
@@ -145,0 +148,0 @@ testTranspile(` |
@@ -242,2 +242,6 @@ import type { CodeAction, CompletionItem, Position, Range, SignatureInformation } from 'vscode-languageserver'; | ||
/** | ||
* Find a list of files in the program that have a function with the given name (case INsensitive) | ||
*/ | ||
findFilesForClass(className: string): (BrsFile | XmlFile)[]; | ||
/** | ||
* Get a map of the manifest information | ||
@@ -244,0 +248,0 @@ */ |
@@ -510,2 +510,3 @@ "use strict"; | ||
getStatementsForXmlFile(scope, filterName) { | ||
var _a, _b; | ||
let results = new Map(); | ||
@@ -517,3 +518,3 @@ const filesSearched = new Set(); | ||
while (reflection_1.isXmlScope(currentScope)) { | ||
for (let name of currentScope.xmlFile.ast.component.api.functions.map((f) => f.name)) { | ||
for (let name of (_b = (_a = currentScope.xmlFile.ast.component.api) === null || _a === void 0 ? void 0 : _a.functions.map((f) => f.name)) !== null && _b !== void 0 ? _b : []) { | ||
if (!filterName || name === filterName) { | ||
@@ -1004,2 +1005,20 @@ funcNames.add(name); | ||
/** | ||
* Find a list of files in the program that have a function with the given name (case INsensitive) | ||
*/ | ||
findFilesForClass(className) { | ||
const files = []; | ||
const lowerClassName = className.toLowerCase(); | ||
//find every file with this class defined | ||
for (const file of Object.values(this.files)) { | ||
if (reflection_1.isBrsFile(file)) { | ||
//TODO handle namespace-relative classes | ||
//if the file has a function with this name | ||
if (file.parser.references.classStatementLookup.get(lowerClassName) !== undefined) { | ||
files.push(file); | ||
} | ||
} | ||
} | ||
return files; | ||
} | ||
/** | ||
* Get a map of the manifest information | ||
@@ -1006,0 +1025,0 @@ */ |
{ | ||
"name": "brighterscript", | ||
"version": "0.35.0", | ||
"version": "0.36.0", | ||
"description": "A superset of Roku's BrightScript language.", | ||
@@ -5,0 +5,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2715186
38300