Skip to content

Commit

Permalink
Merge pull request #194 from KxSystems/ecmel-linter
Browse files Browse the repository at this point in the history
[LS] Rename variables and Linter based on parser
  • Loading branch information
ecmel authored Dec 4, 2023
2 parents 00eff69 + b833098 commit 30a94f0
Show file tree
Hide file tree
Showing 21 changed files with 1,981 additions and 238 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,13 @@ A kdb language server is bundled with the kdb VS Code extension. It offers vario
- [Syntax highlighting and linting](#syntax-highlighting)
- [Code navigation](#code-navigation)
- [Code completion](#code-completion)
- [Rename symbol](#rename-symbol)

### Syntax highlighting

The extension provides keyword syntax highlighting, comments and linting help.

![Syntax Highlighting](https://github.com/KxSystems/kx-vscode/blob/main/img/syntax-highlighting.png?raw=true)
![Syntax Highlighting and Linting](https://github.com/KxSystems/kx-vscode/blob/main/img/syntax-highlighting.png?raw=true)

![Linting](https://github.com/KxSystems/kx-vscode/blob/main/img/linting.png?raw=true)

Expand Down Expand Up @@ -227,6 +228,12 @@ While developing q scripts, the kdb VS Code extension supports:

- Autocomplete for local and remotely connected q processes

### Rename Symbol

Supports renaming symbols in text editor. Right-click and select "Rename Symbol" on any identifier and extension will rename it.

![Rename](https://github.com/KxSystems/kx-vscode/blob/main/img/rename.png?raw=true)

## Execute code

Leaning on VS Code's extensive integrations with SCMs, all code is typically stored and loaded into a VS Code workspace. From there, the kdb VS Code extension allows you execute code against both kdb processes, and kdb Insights Enterprise endpoints.
Expand Down Expand Up @@ -307,6 +314,12 @@ All query executions happen remotely from the kdb VS Code extension either again

![kdb results view](https://github.com/KxSystems/kx-vscode/blob/main/img/kdbview-results.png?raw=true)

## q REPL

q REPL terminal can be started from the command prompt by searching "q REPL".

![REPL](https://github.com/KxSystems/kx-vscode/blob/main/img/repl.png?raw=true)

## Settings

To update kdb VS Code settings, search for `kdb` from _Preferences_ > _Settings_, or right-click the settings icon in kdb VS Code marketplace panel and choose _Extension Settings_.
Expand Down
Binary file modified img/linting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/rename.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/repl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions server/src/linter/assign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* 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 { Entity, EntityType, QAst, getNameScope, isLiteral } from "../parser";

export function assignReservedWord({ assign }: QAst): Entity[] {
return assign.filter((entity) => entity.type === EntityType.KEYWORD);
}

export function invalidAssign({ assign }: QAst): Entity[] {
return assign.filter((entity) => isLiteral(entity));
}

export function declaredAfterUse({ script, assign }: QAst): Entity[] {
return script.filter((entity, index) => {
if (entity.type === EntityType.IDENTIFIER) {
const declared = assign.find(
(symbol) =>
getNameScope(symbol) === getNameScope(entity) &&
symbol.image === entity.image
);
return declared && script.indexOf(declared) > index;
}
return false;
});
}

export function unusedParam({ script, assign }: QAst): Entity[] {
return assign
.filter(
(entity) =>
entity.type === EntityType.IDENTIFIER &&
entity.scope?.type === EntityType.LBRACKET
)
.filter(
(entity) =>
!script.find(
(symbol) =>
symbol !== entity &&
symbol.image === entity.image &&
symbol.type === EntityType.IDENTIFIER &&
getNameScope(symbol) === getNameScope(entity)
)
);
}

export function unusedVar({ script, assign }: QAst): Entity[] {
const locals = assign.filter((entity) => getNameScope(entity));

return assign
.filter(
(entity) =>
entity.type === EntityType.IDENTIFIER &&
entity.scope?.type !== EntityType.LBRACKET
)
.filter(
(entity) =>
!script.find(
(symbol) =>
symbol !== entity &&
symbol.image === entity.image &&
symbol.type === EntityType.IDENTIFIER &&
(getNameScope(symbol) === getNameScope(entity) ||
!locals.find((local) => local.image === symbol.image))
)
);
}
49 changes: 49 additions & 0 deletions server/src/linter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { Entity, QAst } from "../parser";
import { RuleSeverity, Rules } from "./rules";

const enabled = [
"ASSIGN_RESERVED_WORD",
"INVALID_ASSIGN",
//"DECLARED_AFTER_USE",
"UNUSED_PARAM",
"UNUSED_VAR",
"LINE_LENGTH",
"TOO_MANY_CONSTANTS",
"TOO_MANY_GLOBALS",
"TOO_MANY_LOCALS",
"DEPRECATED_DATETIME",
];

export interface LintResult {
name: string;
message: string;
severity: RuleSeverity;
problems: Entity[];
}

export function lint(ast: QAst) {
return Rules.filter((rule) => enabled.find((name) => rule.name === name))
.map((rule) => {
const result: LintResult = {
name: rule.name,
message: rule.message,
severity: rule.severity,
problems: rule.check(ast),
};
return result;
})
.filter((result) => result.problems.length > 0);
}
124 changes: 124 additions & 0 deletions server/src/linter/limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 { Entity, EntityType, QAst, getNameScope } from "../parser";

const DEFAULT_MAX_LINE_LENGTH = 200;
const DEFAULT_MAX_CONSTANTS = 255;
const DEFAULT_MAX_GLOBALS = 255;
const DEFAULT_MAX_LOCALS = 255;

export function lineLength({ script }: QAst): Entity[] {
const problems: Entity[] = [];

const symbols = script.filter(
(entity) => entity.type === EntityType.ENDOFLINE
);

for (let i = 0; i < symbols.length; i++) {
const start = i === 0 ? 0 : symbols[i - 1].endOffset;

if (symbols[i].endOffset - start > DEFAULT_MAX_LINE_LENGTH + 1) {
problems.push(symbols[i]);
}
}

return problems;
}

export function tooManyConstants({ script }: QAst): Entity[] {
const counts = new Map<Entity, number>();

script
.filter((entity) => getNameScope(entity))
.forEach((entity) => {
const scope = getNameScope(entity);
if (scope) {
let count = counts.get(scope);
if (!count) {
count = 0;
}
count++;
counts.set(scope, count);
}
});

const problems: Entity[] = [];

for (const entry of counts.entries()) {
if (entry[1] > DEFAULT_MAX_CONSTANTS) {
problems.push(entry[0]);
}
}

return problems;
}

export function tooManyGlobals({ script, assign }: QAst): Entity[] {
const counts = new Map<Entity, number>();

const globals = assign.filter((entity) => !getNameScope(entity));

script
.filter((entity) => getNameScope(entity))
.filter((entity) => globals.find((global) => global.image === entity.image))
.forEach((entity) => {
const scope = getNameScope(entity);
if (scope) {
let count = counts.get(scope);
if (!count) {
count = 0;
}
count++;
counts.set(scope, count);
}
});

const problems: Entity[] = [];

for (const entry of counts.entries()) {
if (entry[1] > DEFAULT_MAX_GLOBALS) {
problems.push(entry[0]);
}
}

return problems;
}

export function tooManyLocals({ assign }: QAst): Entity[] {
const counts = new Map<Entity, number>();

assign
.filter((entity) => getNameScope(entity))
.forEach((entity) => {
const scope = getNameScope(entity);
if (scope) {
let count = counts.get(scope);
if (!count) {
count = 0;
}
count++;
counts.set(scope, count);
}
});

const problems: Entity[] = [];

for (const entry of counts.entries()) {
if (entry[1] > DEFAULT_MAX_LOCALS) {
problems.push(entry[0]);
}
}

return problems;
}
18 changes: 18 additions & 0 deletions server/src/linter/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 { Entity, EntityType, QAst } from "../parser";

export function deprecatedDatetime({ script }: QAst): Entity[] {
return script.filter((entity) => entity.type === EntityType.DATETIME_LITERAL);
}
Loading

0 comments on commit 30a94f0

Please sign in to comment.