diff --git a/README.md b/README.md
index 1acab8f..2ae4a66 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,16 @@ Profile Scripting Language functionality for Visual Studio Code.
## Dependencies
-* Visual Studio Code version 1.23.0 (April 2018) or higher
+* Visual Studio Code version 1.23.0 (April 2018) or higher.
-## Configuration
+## Avaliable Settings
+
+* `psl.lint` Whether to lint files written in PSL. The default value is `config`, which means linting only activates when the `psl-lint.json` config file is present. [Read more here](#psl-lint).
+* `psl.previewFeatures` Set true to enable the latest developing features (requires restart). Default value is false.
+* `psl.trailingNewline` Adds a trailing newline after a "Get" or "Refresh". The default behavior is to not change the output.
+* `psl.documentationServer` HTTP POST endpoint that responds with PSL documentation in markdown format.
+
+## Environment Configuration
Locate the button at the bottom-right corner titled `Configure Environments`. If the button is not visible, use the Command Palette (F1 or Ctrl+Shift+P) to find the `PSL: Configure Environment` command. A JSON object of the following form will appear:
@@ -44,6 +51,7 @@ The extension is able to communicate with Host via MRPC121 to do the following:
* Test Compile .PROC and .PSL files
* Compile and Link .PROC and .PSL files
* Run .PROC and .PSL files
+* [Code Quality checks via psl-lint](#psl-lint)
These commands can be executed via the Command Pallette (F1 or Ctrl+Shift+P), icons at the top-right corner of the document, right-clicking the document, or right-clicking the file in the Side Bar Explorer.
@@ -54,14 +62,35 @@ Basic language features also exist for files written in PSL, data configuration,
These features include:
* Syntax coloring
-* Property and Label outline for PSL files (access by Ctrl+Shift+O or with [this extension](https://marketplace.visualstudio.com/items?itemName=patrys.vscode-code-outline))
-* Auto-complete for Record objects in PSL (activated by the `.` operator or by Ctrl+Space)
+* Property and Label outline for PSL files (access by Ctrl+Shift+O or by enabling the built-in outline).
+* Code Completion, Hoves, and Go-To Definition by activating `psl.previewFeatures`.
* Highlighting and Hover information for editing data configuration files
* Code snippets for loops, comments, and table/column definitions
+### psl-lint
+
+This extension includes support for checking PSL against common coding standards. The setting `psl.lint` is by default set to `config`, meaning the linting module will activate upon finding a `psl-lint.json` configuration file. Here is a sample:
+
+```
+{
+ "version": 1,
+ "include": {
+ "Z*": ["*"],
+ "*": ["TodoInfo"]
+ },
+ "exclude": {
+ "ZRPC*.PROC": ["MemberCamelCase"]
+ }
+}
+```
+
+Within `include` and `exclude` mappings from filename patterns to rules. These are glob-style patterns ("Z*" will match all files that start with Z). The rules are written in an array, and must be explicitly stated. The only exception is the "*" rule, which matches all rules.
+
+[For more information about which rules are available, and how the linting can be used outside of vscode, visit the package at npm](https://www.npmjs.com/package/psl-lint).
+
## Development
-If you would like to join the development of this extension, you will need to install [node.js](https://nodejs.org/en/) (with NPM) in order to install the dependencies.
+If you would like to join the development of this extension, you will need to install [node.js](https://nodejs.org/en/) (with npm) in order to install the dependencies.
Once you clone the project, from the command line in the root of this project, run `npm install`.
diff --git a/src/pslLint/activate.ts b/src/pslLint/activate.ts
index 5fc40fa..cab6e8e 100644
--- a/src/pslLint/activate.ts
+++ b/src/pslLint/activate.ts
@@ -1,14 +1,12 @@
import * as path from 'path';
+import { ParsedDocument } from '../parser/parser';
import {
- DeclarationRule, Diagnostic, FileDefinitionRule, MemberRule, MethodRule, ParameterRule,
- ProfileComponent, ProfileComponentRule, PropertyRule, PslRule,
+ DeclarationRule, DeclarationRuleConstructor, Diagnostic, FileDefinitionRule, FileDefinitionRuleConstructor,
+ MemberRule, MemberRuleConstructor, MethodRule, MethodRuleConstructor, ParameterRule, ParameterRuleConstructor,
+ ProfileComponent, ProfileComponentRule, ProfileComponentRuleConstructor, PropertyRule, PropertyRuleConstructor,
+ PslRule, PslRuleConstructor,
} from './api';
import { getConfig, matchConfig } from './config';
-
-/**
- * Import rules here.
- */
-import { ParsedDocument } from '../parser/parser';
import {
MemberCamelCase, MemberLength, MemberLiteralCase,
MemberStartsWithV, PropertyIsDummy, PropertyIsDuplicate,
@@ -20,36 +18,33 @@ import { RuntimeStart } from './runtime';
import { TblColDocumentation } from './tblcolDoc';
import { TodoInfo } from './todos';
-/**
- * Add new rules here to have them checked at the appropriate time.
- */
-const componentRules: ProfileComponentRule[] = [];
-const fileDefinitionRules: FileDefinitionRule[] = [
- new TblColDocumentation(),
+const componentRuleConstructors: ProfileComponentRuleConstructor[] = [];
+const fileDefinitionRuleConstructors: FileDefinitionRuleConstructor[] = [
+ TblColDocumentation,
];
-const pslRules: PslRule[] = [
- new TodoInfo(),
+const pslRuleConstructors: PslRuleConstructor[] = [
+ TodoInfo,
];
-const memberRules: MemberRule[] = [
- new MemberCamelCase(),
- new MemberLength(),
- new MemberStartsWithV(),
- new MemberLiteralCase(),
+const memberRuleConstructors: MemberRuleConstructor[] = [
+ MemberCamelCase,
+ MemberLength,
+ MemberStartsWithV,
+ MemberLiteralCase,
];
-const methodRules: MethodRule[] = [
- new MethodDocumentation(),
- new MethodSeparator(),
- new MethodParametersOnNewLine(),
- new RuntimeStart(),
- new MultiLineDeclare(),
- new TwoEmptyLines(),
+const methodRuleConstructors: MethodRuleConstructor[] = [
+ MethodDocumentation,
+ MethodSeparator,
+ MethodParametersOnNewLine,
+ RuntimeStart,
+ MultiLineDeclare,
+ TwoEmptyLines,
];
-const propertyRules: PropertyRule[] = [
- new PropertyIsDummy(),
- new PropertyIsDuplicate(),
+const propertyRuleConstructors: PropertyRuleConstructor[] = [
+ PropertyIsDummy,
+ PropertyIsDuplicate,
];
-const declarationRules: DeclarationRule[] = [];
-const parameterRules: ParameterRule[] = [];
+const declarationRuleConstructors: DeclarationRuleConstructor[] = [];
+const parameterRuleConstructors: ParameterRuleConstructor[] = [];
export function getDiagnostics(
profileComponent: ProfileComponent,
@@ -61,11 +56,10 @@ export function getDiagnostics(
}
/**
- * Interface for adding and executing rules.
+ * Manages which rules need to be applied to a given component.
*/
class RuleSubscription {
- private diagnostics: Diagnostic[];
private componentRules: ProfileComponentRule[];
private pslRules: PslRule[];
private fileDefinitionRules: FileDefinitionRule[];
@@ -76,81 +70,80 @@ class RuleSubscription {
private parameterRules: ParameterRule[];
constructor(private profileComponent: ProfileComponent, private parsedDocument?: ParsedDocument, useConfig?: boolean) {
- this.diagnostics = [];
-
const config = useConfig ? getConfig(this.profileComponent.fsPath) : undefined;
- const initializeRules = (rules: ProfileComponentRule[]) => {
- return rules.filter(rule => {
- if (!config) return true;
- return matchConfig(path.basename(this.profileComponent.fsPath), rule.ruleName, config);
- }).map(rule => {
- rule.profileComponent = this.profileComponent;
- return rule;
+ const filterRule = (ruleCtor: ProfileComponentRuleConstructor | PslRuleConstructor) => {
+ if (!useConfig) return true;
+ if (!config) return false;
+ return matchConfig(path.basename(this.profileComponent.fsPath), ruleCtor.name, config);
+ };
+ const initializeRules = (ruleCtors: ProfileComponentRuleConstructor[]) => {
+ return ruleCtors.filter(filterRule).map(ruleCtor => {
+ return new ruleCtor(this.profileComponent);
});
};
- const initializePslRules = (rules: PslRule[]) => {
- const componentInitialized = initializeRules(rules) as PslRule[];
- const pslParsedDocument = this.parsedDocument as ParsedDocument;
- return componentInitialized.map(rule => {
- rule.parsedDocument = pslParsedDocument;
- return rule;
+ const initializePslRules = (ruleCtors: PslRuleConstructor[]) => {
+ return ruleCtors.filter(filterRule).map(ruleCtor => {
+ const pslParsedDocument = this.parsedDocument as ParsedDocument;
+ return new ruleCtor(this.profileComponent, pslParsedDocument);
});
};
- this.componentRules = initializeRules(componentRules);
- this.fileDefinitionRules = initializeRules(fileDefinitionRules);
- this.pslRules = initializePslRules(pslRules);
- this.methodRules = initializePslRules(methodRules);
- this.memberRules = initializePslRules(memberRules);
- this.propertyRules = initializePslRules(propertyRules);
- this.declarationRules = initializePslRules(declarationRules);
- this.parameterRules = initializePslRules(parameterRules);
+ this.componentRules = initializeRules(componentRuleConstructors);
+ this.fileDefinitionRules = initializeRules(fileDefinitionRuleConstructors);
+ this.pslRules = initializePslRules(pslRuleConstructors);
+ this.methodRules = initializePslRules(methodRuleConstructors);
+ this.memberRules = initializePslRules(memberRuleConstructors);
+ this.propertyRules = initializePslRules(propertyRuleConstructors);
+ this.declarationRules = initializePslRules(declarationRuleConstructors);
+ this.parameterRules = initializePslRules(parameterRuleConstructors);
}
reportRules(): Diagnostic[] {
- const addDiagnostics = (rules: ProfileComponentRule[], ...args: any[]) => {
- rules.forEach(rule => this.diagnostics.push(...rule.report(...args)));
+ const diagnostics: Diagnostic[] = [];
+
+ const collectDiagnostics = (rules: ProfileComponentRule[], ...args: any[]) => {
+ rules.forEach(rule => diagnostics.push(...rule.report(...args)));
};
- addDiagnostics(this.componentRules);
+ collectDiagnostics(this.componentRules);
if (ProfileComponent.isFileDefinition(this.profileComponent.fsPath)) {
- addDiagnostics(this.fileDefinitionRules);
+ collectDiagnostics(this.fileDefinitionRules);
}
if (ProfileComponent.isPsl(this.profileComponent.fsPath)) {
- addDiagnostics(this.pslRules);
+ collectDiagnostics(this.pslRules);
const parsedDocument = this.parsedDocument as ParsedDocument;
for (const property of parsedDocument.properties) {
- addDiagnostics(this.memberRules, property);
- addDiagnostics(this.propertyRules, property);
+ collectDiagnostics(this.memberRules, property);
+ collectDiagnostics(this.propertyRules, property);
}
for (const declaration of parsedDocument.declarations) {
- addDiagnostics(this.memberRules, declaration);
- addDiagnostics(this.declarationRules, declaration);
+ collectDiagnostics(this.memberRules, declaration);
+ collectDiagnostics(this.declarationRules, declaration);
}
for (const method of parsedDocument.methods) {
- addDiagnostics(this.memberRules, method);
- addDiagnostics(this.methodRules, method);
+ collectDiagnostics(this.memberRules, method);
+ collectDiagnostics(this.methodRules, method);
for (const parameter of method.parameters) {
- addDiagnostics(this.memberRules, parameter);
- addDiagnostics(this.parameterRules, parameter, method);
+ collectDiagnostics(this.memberRules, parameter);
+ collectDiagnostics(this.parameterRules, parameter, method);
}
for (const declaration of method.declarations) {
- addDiagnostics(this.memberRules, declaration);
- addDiagnostics(this.declarationRules, declaration, method);
+ collectDiagnostics(this.memberRules, declaration);
+ collectDiagnostics(this.declarationRules, declaration, method);
}
}
}
- return this.diagnostics;
+ return diagnostics;
}
}
diff --git a/src/pslLint/api.ts b/src/pslLint/api.ts
index fcbae73..9a7acd3 100644
--- a/src/pslLint/api.ts
+++ b/src/pslLint/api.ts
@@ -113,10 +113,21 @@ export class DiagnosticRelatedInformation {
export abstract class ProfileComponentRule {
+ /**
+ * The rule name as it is known in the `psl-lint.json` configuration.
+ * This is automatically the same as the rule class name.
+ */
readonly ruleName: string = this.constructor.name;
+ /**
+ * The current component being checked.
+ */
profileComponent: ProfileComponent;
+ constructor(profileComponent: ProfileComponent) {
+ this.profileComponent = profileComponent;
+ }
+
abstract report(...args: any[]): Diagnostic[];
}
@@ -124,31 +135,77 @@ export abstract class FileDefinitionRule extends ProfileComponentRule { }
export abstract class PslRule extends ProfileComponentRule {
+ /**
+ * A parsed version of a PSL component.
+ */
parsedDocument: ParsedDocument;
- abstract report(...args: any[]): Diagnostic[];
+ constructor(profileComponent: ProfileComponent, parsedDocument: ParsedDocument) {
+ super(profileComponent);
+ this.parsedDocument = parsedDocument;
+ }
+
}
+/**
+ * A rule that apply to a Property, Declaration, Parameter, or Method equally.
+ */
export abstract class MemberRule extends PslRule {
abstract report(member: Member): Diagnostic[];
}
+/**
+ * A rule that only apply to a Property.
+ */
export abstract class PropertyRule extends PslRule {
abstract report(property: Property): Diagnostic[];
}
-
+/**
+ * A rule that only apply to a Method.
+ */
export abstract class MethodRule extends PslRule {
abstract report(method: Method): Diagnostic[];
}
-
+/**
+ * A rule that only apply to a Parameter.
+ */
export abstract class ParameterRule extends PslRule {
abstract report(parameter: Parameter, method: Method): Diagnostic[];
}
-
+/**
+ * A rule that only apply to a Declaration.
+ */
export abstract class DeclarationRule extends PslRule {
abstract report(declaration: Declaration, method?: Method): Diagnostic[];
}
+export type FileDefinitionRuleConstructor = new (profileComponent: ProfileComponent) => FileDefinitionRule;
+export type ProfileComponentRuleConstructor = new (profileComponent: ProfileComponent) => ProfileComponentRule;
+export type PslRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => PslRule;
+export type MemberRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => MemberRule;
+export type PropertyRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => PropertyRule;
+export type MethodRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => MethodRule;
+export type ParameterRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => ParameterRule;
+export type DeclarationRuleConstructor = new (
+ profileComponent: ProfileComponent,
+ parsedDocument: ParsedDocument,
+) => DeclarationRule;
+
type GetTextMethod = (lineNumber: number) => string;
/**
@@ -156,25 +213,38 @@ type GetTextMethod = (lineNumber: number) => string;
* The file may be PSL or non-PSL (such as a TBL or COL).
*/
export class ProfileComponent {
-
+ /*
+ * Utility method to check a path.
+ */
static isPsl(fsPath: string): boolean {
return path.extname(fsPath) === '.PROC'
|| path.extname(fsPath) === '.BATCH'
|| path.extname(fsPath) === '.TRIG'
|| path.extname(fsPath).toUpperCase() === '.PSL';
}
-
+ /*
+ * Utility method to check a path.
+ */
static isFileDefinition(fsPath: string): boolean {
return path.extname(fsPath) === '.TBL'
|| path.extname(fsPath) === '.COL';
}
-
+ /*
+ * Utility method to check a path.
+ */
static isProfileComponent(fsPath: string): boolean {
return ProfileComponent.isPsl(fsPath)
|| ProfileComponent.isFileDefinition(fsPath);
}
+ /**
+ * The path on the file system to the component.
+ */
fsPath: string;
+
+ /**
+ * The text contents of the component.
+ */
textDocument: string;
private indexedDocument?: Map;
diff --git a/src/pslLint/cli/README.md b/src/pslLint/cli/README.md
index eae911f..d27b93a 100644
--- a/src/pslLint/cli/README.md
+++ b/src/pslLint/cli/README.md
@@ -15,12 +15,29 @@ This module works by adding rules that are automatically checked at the appropri
* MethodSeparator
* MultiLineDeclare
* PropertyIsDummy
-* PropertyIsDuplicate
* RuntimeStart
* TblColDocumentation
* TodoInfo
* TwoEmptyLines
+## Configuration
+
+By creating a file `psl-lint.json` at the root of the project, you can control the behavior of the linter. The format of the file is as follows:
+
+```
+{
+ "version": 1,
+ "include": {
+ "Z*": ["*"],
+ "*": ["TodoInfo"]
+ },
+ "exclude": {
+ "ZRPC*.PROC": ["MemberCamelCase"]
+ }
+}
+```
+
+Within `include` and `exclude` are filename-to-rules mappings. The filenames can be glob patterns ("Z*" will match all files that start with Z). The rules are written in an array, and must be explicitly stated. The only exception is the "*" rule, which matches all rules.
## Contributing
diff --git a/tslint.json b/tslint.json
index 881607c..40efb64 100644
--- a/tslint.json
+++ b/tslint.json
@@ -43,7 +43,8 @@
],
"no-console": [
false
- ]
+ ],
+ "array-type": [true, "array"]
},
"defaultSeverity": "warn"
}