diff --git a/server/src/linter/assign.ts b/server/src/linter/assign.ts deleted file mode 100644 index 7f105fe0..00000000 --- a/server/src/linter/assign.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 1998-2023 Kx Systems Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -import { Token, TokenType, QAst, scope } from "../parser"; - -export function assignReservedWord({ assign }: QAst): Token[] { - return assign.filter((entity) => entity.type === TokenType.KEYWORD); -} - -export function invalidAssign({ assign }: QAst): Token[] { - return assign.filter((entity) => entity.type === TokenType.LITERAL); -} - -export function unusedParam({ script, assign }: QAst): Token[] { - assign = assign.filter( - (token) => token.type === TokenType.IDENTIFIER && token.tag === "ARGUMENT", - ); - - script = script.filter( - (token) => - token.tag !== "ASSIGNED" && - token.tag !== "ARGUMENT" && - token.type === TokenType.IDENTIFIER && - assign.find((symbol) => symbol.image === token.image), - ); - - assign = assign.filter( - (token) => - !script.find( - (symbol) => - symbol.image === token.image && scope(symbol) === scope(token), - ), - ); - - return assign; -} - -export function unusedVar({ script, assign }: QAst): Token[] { - const locals = assign.filter( - (token) => token.type === TokenType.IDENTIFIER && scope(token), - ); - - assign = assign.filter( - (token) => token.type === TokenType.IDENTIFIER && token.tag === "ASSIGNED", - ); - - script = script.filter( - (token) => - token.tag !== "ASSIGNED" && - token.tag !== "ARGUMENT" && - token.type === TokenType.IDENTIFIER && - assign.find((symbol) => symbol.image === token.image), - ); - - assign = assign.filter( - (token) => - !script.find( - (symbol) => - symbol.image === token.image && - (scope(symbol) === scope(token) || - (!scope(token) && - !locals.find( - (local) => - local.image === token.image && scope(local) === scope(symbol), - ))), - ), - ); - - return assign; -} - -export function declaredAfterUse({ script, assign }: QAst): Token[] { - return assign - .filter((token) => token.tag === "ASSIGNED" && !scope(token)) - .filter((token) => { - const found = script.find( - (entity) => - entity.type === TokenType.IDENTIFIER && - entity.tag !== "ASSIGNED" && - entity.image === token.image && - !scope(entity), - ); - - if (found) { - const group = scope(found, [TokenType.GROUP]); - return group && group === scope(token, [TokenType.GROUP]) - ? found.statement > token.statement - : found.statement < token.statement; - } - - return false; - }); -} diff --git a/server/src/linter/rules.ts b/server/src/linter/rules.ts deleted file mode 100644 index 2bfa6933..00000000 --- a/server/src/linter/rules.ts +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright (c) 1998-2023 Kx Systems Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the - * License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -import { Token, QAst } from "../parser"; -import { - assignReservedWord, - declaredAfterUse, - invalidAssign, - unusedParam, - unusedVar, -} from "./assign"; -import { - tooManyArguments, - tooManyConstants, - tooManyGlobals, - tooManyLocals, -} from "./limit"; -import { deprecatedDatetime, fixedSeed, invalidEscape } from "./other"; - -export enum RuleSeverity { - ERROR = "ERROR", - WARNING = "WARNING", - INFO = "INFO", - HINT = "HINT", -} - -export interface LinterRule { - name: string; - message: string; - severity: RuleSeverity; - check: (ast: QAst) => Token[]; -} - -const check = () => []; - -const AssignReservedWordRule: LinterRule = { - name: "ASSIGN_RESERVED_WORD", - message: "Assignment to a reserved word", - severity: RuleSeverity.ERROR, - check: assignReservedWord, -}; - -const CondEvenArgsRule: LinterRule = { - name: "COND_EVENARGS", - message: "Conditional $ should not be used with an even number of arguments", - severity: RuleSeverity.ERROR, - check, -}; - -const DeclaredAfterUseRule: LinterRule = { - name: "DECLARED_AFTER_USE", - message: "The variable was declared after being used", - severity: RuleSeverity.HINT, - check: declaredAfterUse, -}; - -const GlobalPeachRule: LinterRule = { - name: "GLOBAL_PEACH", - message: "Modifying globals inside a peach statement is not allowed", - severity: RuleSeverity.ERROR, - check, -}; - -const InvalidAdverbRule: LinterRule = { - name: "INVALID_ADVERB", - message: "A binary adverb cannot be applied to a unary function", - severity: RuleSeverity.ERROR, - check, -}; - -const InvalidAssignRule: LinterRule = { - name: "INVALID_ASSIGN", - message: "Attempt to assign to a string, symbol, or number", - severity: RuleSeverity.ERROR, - check: invalidAssign, -}; - -const InvalidEscapeRule: LinterRule = { - name: "INVALID_ESCAPE", - message: - 'Invalid Escape Sequence: Valid escape sequences are: \\n,\\r,\\t,\\\\,\\/,\\" and three digit octal sequences \\377 or smaller', - severity: RuleSeverity.HINT, - check: invalidEscape, -}; - -const InvalidQukeRule: LinterRule = { - name: "INVALID_QUKE", - message: "A quke file was improperly formatted", - severity: RuleSeverity.ERROR, - check, -}; - -const OverwriteArtifactRule: LinterRule = { - name: "OVERWRITE_ARTIFACT", - message: "Variable assignment overwrites namespace or artifact", - severity: RuleSeverity.ERROR, - check, -}; - -const StatementInExprRule: LinterRule = { - name: "STATEMENT_IN_EXPR", - message: - "If, while, or do statement used in expression, possible missing semicolon", - severity: RuleSeverity.ERROR, - check, -}; - -const ReservedNameRule: LinterRule = { - name: "RESERVED_NAME", - message: "File has reserved name", - severity: RuleSeverity.ERROR, - check, -}; - -const TooManyConstantsRule: LinterRule = { - name: "TOO_MANY_CONSTANTS", - message: "Too many constants in a function", - severity: RuleSeverity.ERROR, - check: tooManyConstants, -}; - -const TooManyGlobalsRule: LinterRule = { - name: "TOO_MANY_GLOBALS", - message: "Too many globals in a function", - severity: RuleSeverity.ERROR, - check: tooManyGlobals, -}; - -const UnindentedCodeRule: LinterRule = { - name: "UNINDENTED_CODE", - message: "Any multiline expression must be indented after the first line", - severity: RuleSeverity.ERROR, - check, -}; - -const TooManyLocalsRule: LinterRule = { - name: "TOO_MANY_LOCALS", - message: "Too many locals in a function", - severity: RuleSeverity.ERROR, - check: tooManyLocals, -}; - -const TooManyArgumentsRule: LinterRule = { - name: "TOO_MANY_ARGUMENTS", - message: "Too many arguments in a function", - severity: RuleSeverity.ERROR, - check: tooManyArguments, -}; - -const BackwardCompatibilityRule: LinterRule = { - name: "BACKWARD_COMPATIBILITY", - message: - "This function has backward compatibility issues with kdb versions less than 3.6", - severity: RuleSeverity.WARNING, - check, -}; - -const CastTypeNumericalRule: LinterRule = { - name: "CAST_TYPE_NUMERICAL", - message: - "Casting using a short to indicate cast type is unnecessarily unclear. Another form is advised", - severity: RuleSeverity.WARNING, - check, -}; - -const ConditionallyDeclaredRule: LinterRule = { - name: "CONDITIONALLY_DECLARED", - message: - "This variable may be undefined at this point, as it was only declared conditionally", - severity: RuleSeverity.WARNING, - check, -}; - -const DebugFunctionRule: LinterRule = { - name: "DEBUG_FUNCTION", - message: "eval, or value when run on a string literal", - severity: RuleSeverity.WARNING, - check, -}; - -const DeprecatedDatetimeRule: LinterRule = { - name: "DEPRECATED_DATETIME", - message: "Datetime has been deprecated", - severity: RuleSeverity.WARNING, - check: deprecatedDatetime, -}; - -const DeprecatedFunctionRule: LinterRule = { - name: "DEPRECATED_FUNCTION", - message: "This file uses a deprecated function", - severity: RuleSeverity.WARNING, - check, -}; - -const EmptyIfRule: LinterRule = { - name: "EMPTY_IF", - message: "If statement lacks code to execute", - severity: RuleSeverity.WARNING, - check, -}; - -const FixedSeedRule: LinterRule = { - name: "FIXED_SEED", - message: - "Inputting a positive number into ?0Ng will result in the same sequence every run", - severity: RuleSeverity.WARNING, - check: fixedSeed, -}; - -const FunctionStartRule: LinterRule = { - name: "FUNCTION_START", - message: "Function artifact must start with a function", - severity: RuleSeverity.WARNING, - check, -}; - -const InsufficientIndentRule: LinterRule = { - name: "INSUFFICIENT_INDENT", - message: - "Indentation must be equal to or greater than the second line of the function body, and the second line must have an indentation greater than the first line", - severity: RuleSeverity.WARNING, - check, -}; - -const InternalRule: LinterRule = { - name: "INTERNAL", - message: "Reference to an internal api of another module", - severity: RuleSeverity.WARNING, - check, -}; - -const InvalidFunctionRule: LinterRule = { - name: "INVALID_FUNCTION", - message: - "Function artifacts must be lambda definitions, rather than projections, immediately invoked functions, or functions in expressions", - severity: RuleSeverity.WARNING, - check, -}; - -const MalformedSuppressionRule: LinterRule = { - name: "MALFORMED_SUPPRESSION", - message: "Malformed @qlintsuppress tag", - severity: RuleSeverity.WARNING, - check, -}; - -const MissingDependencyRule: LinterRule = { - name: "MISSING_DEPENDENCY", - message: - "Any reference to another namespace should be listed in the dependency list", - severity: RuleSeverity.WARNING, - check, -}; - -const NameCollisionRule: LinterRule = { - name: "NAME_COLLISION", - message: "Executing statement in editor could overwrite global variable", - severity: RuleSeverity.WARNING, - check, -}; - -const NeedExplicitReturnRule: LinterRule = { - name: "NEED_EXPLICIT_RETURN", - message: "Explicit return needed. Otherwise will return generic null", - severity: RuleSeverity.WARNING, - check, -}; - -const PossibleReturnRule: LinterRule = { - name: "POSSIBLE_RETURN", - message: "Assignment statement looks like return", - severity: RuleSeverity.WARNING, - check, -}; - -const UndeclaredVarRule: LinterRule = { - name: "UNDECLARED_VAR", - message: "Undeclared variable in function will be treated as global", - severity: RuleSeverity.WARNING, - check, -}; - -const UnusedInternalRule: LinterRule = { - name: "UNUSED_INTERNAL", - message: - "This function is marked as internal (is part of a sub-namespace i) but was never used within the namespace", - severity: RuleSeverity.WARNING, - check, -}; - -const UnusedParamRule: LinterRule = { - name: "UNUSED_PARAM", - message: "This param was declared then never used", - severity: RuleSeverity.HINT, - check: unusedParam, -}; - -const UnusedVarRule: LinterRule = { - name: "UNUSED_VAR", - message: "This variable was declared then never used", - severity: RuleSeverity.HINT, - check: unusedVar, -}; - -const RandomGuidsRule: LinterRule = { - name: "RANDOM_GUIDS", - message: - "Multiple calls to ?0ng in quick succession, with negative numbers, can produce the same output", - severity: RuleSeverity.WARNING, - check, -}; - -const UnreachableCodeRule: LinterRule = { - name: "UNREACHABLE_CODE", - message: "A preceding return prevents this statement from being reached", - severity: RuleSeverity.WARNING, - check, -}; - -const UnexpectedCondNewline: LinterRule = { - name: "UNEXPECTED_COND_NEWLINE", - message: "Condition should begin on same line as loop or if statement", - severity: RuleSeverity.WARNING, - check, -}; - -const UnparenthesizedJoinRule: LinterRule = { - name: "UNPARENTHESIZED_JOIN", - message: - "A potential join in this QSQL statement will be interpreted as separate statements unless wrapped in parentheses", - severity: RuleSeverity.WARNING, - check, -}; - -const VarQErrorRule: LinterRule = { - name: "VAR_Q_ERROR", - message: - "Variable name the same as q error message. This can cause ambiguous error messages", - severity: RuleSeverity.WARNING, - check, -}; - -const MissingSemicolonRule: LinterRule = { - name: "MISSING_SEMICOLON", - message: - "An apply statement spans multiple lines with the same indentation and an assignment on the second line, potentially indicating a missing semi-colon ", - severity: RuleSeverity.WARNING, - check, -}; - -const MalformedRule: LinterRule = { - name: "MALFORMED_RULE", - message: "Malformed @qlintrule tag", - severity: RuleSeverity.WARNING, - check, -}; - -const TodoRule: LinterRule = { - name: "TODO", - message: "Todo qDoc tag present", - severity: RuleSeverity.WARNING, - check, -}; - -const LineLengthRule: LinterRule = { - name: "LINE_LENGTH", - message: "Maximum line length exceeded", - severity: RuleSeverity.WARNING, - check, -}; - -const DefaultQdocRule: LinterRule = { - name: "DEFAULT_QDOC", - message: "The file has the default documentation", - severity: RuleSeverity.INFO, - check, -}; - -const InvalidKindRule: LinterRule = { - name: "INVALID_KIND", - message: "Invalid qdoc kind in tag", - severity: RuleSeverity.INFO, - check, -}; - -const InvalidTypedefRule: LinterRule = { - name: "INVALID_TYPEDEF", - message: "Invalid typedef tag", - severity: RuleSeverity.INFO, - check, -}; - -const InvalidTagRule: LinterRule = { - name: "INVALID_TAG", - message: "Tag not recognized as valid qDoc tag", - severity: RuleSeverity.INFO, - check, -}; - -const MissingOverviewRule: LinterRule = { - name: "MISSING_OVERVIEW", - message: "Missing @fileOverview tag with associated description", - severity: RuleSeverity.INFO, - check, -}; - -const MissingReturnsRule: LinterRule = { - name: "MISSING_RETURNS", - message: "Missing @returns tag", - severity: RuleSeverity.INFO, - check, -}; - -const MissingTypeRule: LinterRule = { - name: "MISSING_TYPE", - message: "Missing type in returns or param tag", - severity: RuleSeverity.INFO, - check, -}; - -const MultipleReturnsRule: LinterRule = { - name: "MULTIPLE_RETURNS", - message: "Multiple @returns tags", - severity: RuleSeverity.INFO, - check, -}; - -const OurOfOrderParamRule: LinterRule = { - name: "OUT_OF_ORDER_PARAM", - message: "Parameters out of order", - severity: RuleSeverity.INFO, - check, -}; - -const ParamNotInCodeRule: LinterRule = { - name: "PARAM_NOT_IN_CODE", - message: "This param is not in the function", - severity: RuleSeverity.INFO, - check, -}; - -const QdocTypeRule: LinterRule = { - name: "QDOC_TYPE", - message: "Invalid type in tag", - severity: RuleSeverity.INFO, - check, -}; - -const RedundantQlobalAssignRule: LinterRule = { - name: "REDUNDANT_GLOBAL_ASSIGN", - message: - "Using the global amend operator on a fully qualified name is redundant", - severity: RuleSeverity.INFO, - check, -}; - -const UndocumentedParamRule: LinterRule = { - name: "UNDOCUMENTED_PARAM", - message: "Undocumented parameter", - severity: RuleSeverity.INFO, - check, -}; - -const UnusedDependencyRule: LinterRule = { - name: "UNUSED_DEPENDENCY", - message: "Unused dependencies", - severity: RuleSeverity.INFO, - check, -}; - -export const Rules: LinterRule[] = [ - AssignReservedWordRule, - CondEvenArgsRule, - DeclaredAfterUseRule, - GlobalPeachRule, - InvalidAdverbRule, - InvalidAssignRule, - InvalidEscapeRule, - InvalidQukeRule, - OverwriteArtifactRule, - StatementInExprRule, - ReservedNameRule, - TooManyConstantsRule, - TooManyGlobalsRule, - TooManyLocalsRule, - TooManyArgumentsRule, - UnindentedCodeRule, - BackwardCompatibilityRule, - CastTypeNumericalRule, - ConditionallyDeclaredRule, - DebugFunctionRule, - DeprecatedDatetimeRule, - DeprecatedFunctionRule, - EmptyIfRule, - FixedSeedRule, - FunctionStartRule, - InsufficientIndentRule, - InternalRule, - InvalidFunctionRule, - MalformedSuppressionRule, - MissingDependencyRule, - NameCollisionRule, - NeedExplicitReturnRule, - PossibleReturnRule, - UndeclaredVarRule, - UnusedInternalRule, - UnusedParamRule, - UnusedVarRule, - RandomGuidsRule, - UnreachableCodeRule, - UnexpectedCondNewline, - UnparenthesizedJoinRule, - VarQErrorRule, - MissingSemicolonRule, - MalformedRule, - TodoRule, - LineLengthRule, - DefaultQdocRule, - InvalidKindRule, - InvalidTypedefRule, - InvalidTagRule, - MissingOverviewRule, - MissingReturnsRule, - MissingTypeRule, - MultipleReturnsRule, - OurOfOrderParamRule, - ParamNotInCodeRule, - QdocTypeRule, - RedundantQlobalAssignRule, - UndocumentedParamRule, - UnusedDependencyRule, -]; diff --git a/server/src/parser/parser.ts b/server/src/parser/parser.ts index 96a18f8b..ce54fa68 100644 --- a/server/src/parser/parser.ts +++ b/server/src/parser/parser.ts @@ -84,6 +84,8 @@ export interface Token extends IToken { identifierKind?: IdentifierKind; scope?: Token; lambda?: Token; + source?: Token; + namespace?: string; } export function parse(text: string): Token[] { @@ -105,6 +107,7 @@ export function parse(text: string): Token[] { for (let i = 0; i < tokens.length; i++) { token = tokens[i]; + token.namespace = namespace; switch (token.tokenType) { case Identifier: if (argument) { diff --git a/server/src/qLangServer.ts b/server/src/qLangServer.ts index 7fad1f14..a702c5f4 100644 --- a/server/src/qLangServer.ts +++ b/server/src/qLangServer.ts @@ -64,16 +64,6 @@ function isLocal(source: Token, tokens: Token[]) { ); } -function hasPosition(token: Token, position: Position) { - const { start, end } = rangeFromToken(token); - return ( - start.line <= position.line && - end.line >= position.line && - start.character <= position.character && - end.character >= position.character - ); -} - function isAssignable(token: Token) { return ( token.identifierKind !== IdentifierKind.Sql && @@ -81,6 +71,24 @@ function isAssignable(token: Token) { ); } +function positionToToken(position: Position, tokens: Token[]) { + return tokens.find((token) => { + const { start, end } = rangeFromToken(token); + return ( + start.line <= position.line && + end.line >= position.line && + start.character <= position.character && + end.character >= position.character + ); + }); +} + +function getLabel(token: Token, source?: Token): string { + return !token.identifier || source?.namespace === token.namespace + ? token.image + : token.identifier; +} + export default class QLangServer { private declare connection: Connection; private declare params: InitializeParams; @@ -112,18 +120,12 @@ export default class QLangServer { public onDocumentSymbol({ textDocument, }: DocumentSymbolParams): DocumentSymbol[] { - const document = this.documents.get(textDocument.uri); - if (!document) { - return []; - } - - const tokens = parse(document.getText()); - + const tokens = this.parse(textDocument); return tokens .filter((token) => token.kind === TokenKind.Assignment && !token.scope) .map((token) => DocumentSymbol.create( - token.identifier || token.image, + getLabel(token), undefined, token.lambda ? SymbolKind.Object : SymbolKind.Variable, rangeFromToken(token), @@ -151,7 +153,9 @@ export default class QLangServer { } public onReferences({ textDocument, position }: ReferenceParams): Location[] { - return this.findIdentifiers(FindKind.Reference, textDocument, position).map( + const tokens = this.parse(textDocument); + const source = positionToToken(position, tokens); + return this.findIdentifiers(FindKind.Reference, tokens, source).map( (token) => Location.create(textDocument.uri, rangeFromToken(token)), ); } @@ -160,11 +164,11 @@ export default class QLangServer { textDocument, position, }: DefinitionParams): Location[] { - return this.findIdentifiers( - FindKind.Definition, - textDocument, - position, - ).map((token) => Location.create(textDocument.uri, rangeFromToken(token))); + const tokens = this.parse(textDocument); + const source = positionToToken(position, tokens); + return this.findIdentifiers(FindKind.Definition, tokens, source).map( + (token) => Location.create(textDocument.uri, rangeFromToken(token)), + ); } public onRenameRequest({ @@ -172,11 +176,11 @@ export default class QLangServer { position, newName, }: RenameParams): WorkspaceEdit | null { - const edits = this.findIdentifiers( - FindKind.Rename, - textDocument, - position, - ).map((token) => TextEdit.replace(rangeFromToken(token), newName)); + const tokens = this.parse(textDocument); + const source = positionToToken(position, tokens); + const edits = this.findIdentifiers(FindKind.Rename, tokens, source).map( + (token) => TextEdit.replace(rangeFromToken(token), newName), + ); return edits.length === 0 ? null : { @@ -190,33 +194,34 @@ export default class QLangServer { textDocument, position, }: CompletionParams): CompletionItem[] { - return this.findIdentifiers( - FindKind.Completion, - textDocument, - position, - ).map((token) => ({ - label: token.identifier || token.image, - kind: token.lambda - ? CompletionItemKind.Function - : CompletionItemKind.Variable, - })); + const tokens = this.parse(textDocument); + const source = positionToToken(position, tokens); + return this.findIdentifiers(FindKind.Completion, tokens, source).map( + (token) => ({ + label: getLabel(token, source), + kind: token.lambda + ? CompletionItemKind.Function + : CompletionItemKind.Variable, + }), + ); } - private findIdentifiers( - kind: FindKind, - textDocument: TextDocumentIdentifier, - position: Position, - ): Token[] { + private parse(textDocument: TextDocumentIdentifier): Token[] { const document = this.documents.get(textDocument.uri); if (!document) { return []; } - const tokens = parse(document.getText()); - const source = tokens.find((token) => hasPosition(token, position)); - if (!source || !isAssignable(source)) { + return parse(document.getText()); + } + + private findIdentifiers( + kind: FindKind, + tokens: Token[], + source?: Token, + ): Token[] { + if (!source) { return []; } - switch (kind) { case FindKind.Rename: case FindKind.Reference: diff --git a/test/suite/qLangServer.test.ts b/test/suite/qLangServer.test.ts index 380317ab..74a8a774 100644 --- a/test/suite/qLangServer.test.ts +++ b/test/suite/qLangServer.test.ts @@ -18,16 +18,26 @@ import * as sinon from "sinon"; import { Connection, InitializeParams, - Position, - Range, TextDocumentIdentifier, } from "vscode-languageserver"; -import QLangServer from "../../server/src/qLangServer"; import { TextDocument } from "vscode-languageserver-textdocument"; +import QLangServer from "../../server/src/qLangServer"; describe("qLangServer", () => { let server: QLangServer; + function createDocument(content: string) { + content = content.trim(); + const document = TextDocument.create("test.q", "q", 1, content); + const position = document.positionAt(content.length); + const textDocument = TextDocumentIdentifier.create("test.q"); + sinon.stub(server.documents, "get").value(() => document); + return { + textDocument, + position, + }; + } + beforeEach(async () => { const connection = ({ listen() {}, @@ -68,96 +78,73 @@ describe("qLangServer", () => { }); describe("onDocumentSymbol", () => { - it("should return golobals", () => { - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1")); - const result = server.onDocumentSymbol({ textDocument }); - assert.strictEqual(result.length, 1); - assert.strictEqual(result[0].name, "a"); + it("should return symbols", () => { + const params = createDocument("a:1;b:{[c]d:c+1;d};b"); + const result = server.onDocumentSymbol(params); + assert.strictEqual(result.length, 2); }); }); describe("onReferences", () => { - it("should return golobal references", () => { - const position = Position.create(0, 5); - const context = { - includeDeclaration: true, - }; - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1;a")); + it("should return references", () => { + const params = createDocument( + '\\d .a\nsystem "d .a"\na:1;b:{[c]d:c+1;d};b', + ); const result = server.onReferences({ - textDocument, - position, - context, + ...params, + context: { includeDeclaration: true }, }); assert.strictEqual(result.length, 2); - assert.deepStrictEqual(result[0].range, Range.create(0, 0, 0, 1)); - assert.deepStrictEqual(result[1].range, Range.create(0, 4, 0, 5)); + }); + it("should return references for quke", () => { + const params = createDocument(` + feature + before + after + before each + after each + should + expect + a:1;a + `); + const result = server.onReferences({ + ...params, + context: { includeDeclaration: true }, + }); + assert.strictEqual(result.length, 2); + }); + it("should skip table and sql", () => { + const params = createDocument("select a:1 from;([]a:1)"); + const result = server.onReferences({ + ...params, + context: { includeDeclaration: true }, + }); + assert.strictEqual(result.length, 0); }); }); describe("onDefinition", () => { - it("should return golobal definition", () => { - const position = Position.create(0, 5); - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1;a")); - const result = server.onDefinition({ - textDocument, - position, - }); + it("should return definitions", () => { + const params = createDocument("a:1;b:{[c]d:c+1;d};b"); + const result = server.onDefinition(params); assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result[0].range, Range.create(0, 0, 0, 1)); }); }); describe("onRenameRequest", () => { - it("should rename golobal identifiers", () => { - const position = Position.create(0, 5); - const newName = "b"; - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1;a")); - const result = server.onRenameRequest({ - textDocument, - position, - newName, - }); + it("should rename identifiers", () => { + const params = createDocument("a:1;b:{[c]d:c+1;d};b"); + const result = server.onRenameRequest({ ...params, newName: "newName" }); assert.ok(result); - assert.strictEqual(result.changes[textDocument.uri].length, 2); + assert.strictEqual(result.changes[params.textDocument.uri].length, 2); }); }); describe("onCompletion", () => { - it("should complete golobal identifiers", () => { - const position = Position.create(0, 5); - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1;a")); - const result = server.onCompletion({ - textDocument, - position, - }); - assert.strictEqual(result.length, 1); - }); - it("should filter out duplicates", () => { - const position = Position.create(0, 5); - const textDocument = TextDocumentIdentifier.create("test.q"); - sinon - .stub(server.documents, "get") - .value(() => TextDocument.create("test.q", "q", 1, "a:1;a;a:2;a")); - const result = server.onCompletion({ - textDocument, - position, - }); - assert.strictEqual(result.length, 1); + it("should complete identifiers", () => { + const params = createDocument("a:1;b:{[c]d:c+1;d};b"); + const result = server.onCompletion(params); + assert.strictEqual(result.length, 2); }); }); });