Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add linter feature using "Stillat/blade-parser-typescript" parser #13

Merged
merged 2 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Laravel Blade Templates extension for [coc.nvim](https://github.com/neoclide/coc
- Format
- by [blade-formatter](https://github.com/shufo/blade-formatter)
- Lint
- by [laravel-blade-linter](https://github.com/bdelespierre/laravel-blade-linter)
- using [Stillat/blade-parser-typescript](https://github.com/Stillat/blade-parser-typescript)
- Completion
- Blade Snippets Completion
- Blade Directive Completion
Expand Down Expand Up @@ -95,15 +95,11 @@ resources/views/books/**/*

> In coc-blade, there is a code action feature to add a blade comment to disable the formatting.

### linter (laravel-blade-linter)
### linter (using Stillat/blade-parser-typescript)

You will need to have [laravel-blade-linter](https://github.com/bdelespierre/laravel-blade-linter) installed in your "Laravel project".
This feature is enabled by default. If you do not need the linter feature, set `blade.bladeParserLint.enable` to `false`

If "laravel-blade-linter" is not detected, the lint (diagnostics) feature is automatically disabled.

```sh
composer require --dev bdelespierre/laravel-blade-linter
```
- [DEMO](https://github.com/yaegassy/coc-blade/pull/13)

### snippets completion (laravel-blade-snippets-vscode)

Expand Down Expand Up @@ -155,7 +151,11 @@ Parses `bootstrap/cache/livewire-components.php` files and target component clas
- `blade.bladeFormatter.optWrapLineLength`: The length of line wrap size (`--wrap-line-length`), valid type `integer` or `null`, default: `null`
- `blade.bladeFormatter.optWrapAttributes`: The way to wrap attributes (`--wrap-attributes`), valid options `["auto", "force", "force-aligned", "force-expand-multiline", "aligned-multiple", "preserve", "preserve-aligned"]`, valid type `string` or `null`, default: `null`
- `blade.bladeFormatter.optSortTailwindcssClasses`: markdownDescription": "Sort Tailwindcss classes automatically. This option respects `tailwind.config.js` and sort classes according to settings, valid type `boolean` or `null`, default: `null`
- `blade.bladeLinter.enable`: Enable/Disable the linting feature by `laravel-blade-linter`, default: `true`
- `blade.bladeParserLint.enable`: Enable/Disable the linting feature using `stillat-blade-parser`, default: `true`
- `blade.bladeParserLint.debug`: Output the results of the parsing of stillat-blade-parser to the channel log, default: `false`
- `blade.bladeParserLint.optCustomIfs`: A list of custom if directives, default: `[]`
- `blade.bladeParserLint.optDirectives`: A list of directives that can be parsed, default: `[]`
- `blade.bladeParserLint.optIgnoreDirectives`: A list of directive names that should be ignored, default: `[]`

## Commands

Expand All @@ -182,7 +182,7 @@ nmap <silent> gA <Plug>(coc-codeaction)

- [shufo/blade-formatter](https://github.com/shufo/blade-formatter)
- [shufo/vscode-blade-formatter](https://github.com/shufo/vscode-blade-formatter)
- [bdelespierre/laravel-blade-linter](https://github.com/bdelespierre/laravel-blade-linter)
- [Stillat/blade-parser-typescript](https://github.com/Stillat/blade-parser-typescript)
- [onecentlin/laravel-blade-snippets-vscode](https://github.com/onecentlin/laravel-blade-snippets-vscode)

## License
Expand Down
26 changes: 26 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,31 @@
"default": false,
"markdownDescription": "Sort Tailwindcss classes automatically. This option respects `tailwind.config.js` and sort classes according to settings"
},
"blade.bladeParserLint.enable": {
"type": "boolean",
"default": true,
"description": "Enable/Disable the linting feature using stillat-blade-parser"
},
"blade.bladeParserLint.debug": {
"type": "boolean",
"default": false,
"description": "Output the results of the parsing of stillat-blade-parser to the channel log"
},
"blade.bladeParserLint.optCustomIfs": {
"type": "array",
"default": [],
"description": "A list of custom if directives"
},
"blade.bladeParserLint.optDirectives": {
"type": "array",
"default": [],
"description": "A list of directives that can be parsed"
},
"blade.bladeParserLint.optIgnoreDirectives": {
"type": "array",
"default": [],
"description": "A list of directive names that should be ignored"
},
"blade.bladeLinter.enable": {
"type": "boolean",
"default": true,
Expand All @@ -195,6 +220,7 @@
"dependencies": {
"blade-formatter": "^1.26.16",
"ignore": "^5.2.0",
"stillat-blade-parser": "^0.1.0",
"synckit": "^0.6.0"
}
}
20 changes: 20 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ export function getConfigBladeLinterEnable() {
return workspace.getConfiguration('blade').get<boolean>('bladeLinter.enable', true);
}

export function getConfigBladeParserLintEnable() {
return workspace.getConfiguration('blade').get<boolean>('bladeParserLint.enable', true);
}

export function getConfigBladeParserLintDebug() {
return workspace.getConfiguration('blade').get<boolean>('bladeParserLint.debug', false);
}

export function getConfigBladeParserLintOptCustomIfs() {
return workspace.getConfiguration('blade').get<string[]>('bladeParserLint.optCustomIfs', []);
}

export function getConfigBladeParserLintOptDirectives() {
return workspace.getConfiguration('blade').get<string[]>('bladeParserLint.optDirectives', []);
}

export function getConfigBladeParserLintOptIgnoreDirectives() {
return workspace.getConfiguration('blade').get<string[]>('bladeParserLint.optIgnoreDirectives', []);
}

export function getConfigBladeCompletionEnable() {
return workspace.getConfiguration('blade').get<boolean>('completion.enable', true);
}
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as bladeCompletionFeature from './completions/completion';
import * as bladeDefinisionFeature from './definitions/definition';
import * as bladeFormatterDocumantFormattingEditFeature from './documentFormats/documentFormat';
import * as bladeHoverFeature from './hovers/hover';
import * as bladeLinterFeature from './linters/bladeLinter';
import * as bladeParserLintFeature from './linters/bladeParserLint';

export async function activate(context: ExtensionContext): Promise<void> {
if (!getConfigBladeEnable()) return;
Expand All @@ -20,8 +20,8 @@ export async function activate(context: ExtensionContext): Promise<void> {
bladeFormatterRunCommandFeature.register(context, outputChannel);
await bladeCompletionFeature.register(context, outputChannel);
bladeFormatterDocumantFormattingEditFeature.register(context, outputChannel);
bladeLinterFeature.register(context, outputChannel);
bladeHoverFeature.register(context);
bladeDefinisionFeature.register(context);
bladeCodeActionFeature.register(context);
bladeParserLintFeature.register(context, outputChannel);
}
145 changes: 145 additions & 0 deletions src/linters/bladeParserLint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import {
Diagnostic,
DiagnosticCollection,
DiagnosticSeverity,
ExtensionContext,
languages,
OutputChannel,
Position,
Range,
TextDocument,
workspace,
} from 'coc.nvim';

import { BladeDocument } from 'stillat-blade-parser/out/document/bladeDocument';
import { ParserOptions } from 'stillat-blade-parser/out/parser/parserOptions';

import {
getConfigBladeParserLintEnable,
getConfigBladeParserLintDebug,
getConfigBladeParserLintOptCustomIfs,
getConfigBladeParserLintOptDirectives,
getConfigBladeParserLintOptIgnoreDirectives,
} from '../config';

export async function register(context: ExtensionContext, outputChannel: OutputChannel) {
if (getConfigBladeParserLintEnable()) {
const engine = new BladeParserLintEngine(outputChannel);

// onOpen
workspace.documents.map(async (doc) => {
await engine.lint(doc.textDocument);
});
workspace.onDidOpenTextDocument(
async (e) => {
await engine.lint(e);
},
null,
context.subscriptions
);

// onChange
workspace.onDidChangeTextDocument(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async (_e) => {
const doc = await workspace.document;
await engine.lint(doc.textDocument);
},
null,
context.subscriptions
);

// onSave
workspace.onDidSaveTextDocument(
async (e) => {
await engine.lint(e);
},
null,
context.subscriptions
);
}
}

class BladeParserLintEngine {
private collection: DiagnosticCollection;
private outputChannel: OutputChannel;

constructor(outputChannel: OutputChannel) {
this.collection = languages.createDiagnosticCollection('bladeParser');
this.outputChannel = outputChannel;
}

public async lint(textDocument: TextDocument): Promise<void> {
if (textDocument.languageId !== 'blade') return;

const diagnostics: Diagnostic[] = [];
const content = textDocument.getText();

const parserDocument = new BladeDocument();
const parserOptions: ParserOptions = {
customIfs: getConfigBladeParserLintOptCustomIfs(),
directives: getConfigBladeParserLintOptDirectives(),
ignoreDirectives: getConfigBladeParserLintOptIgnoreDirectives(),
};

parserDocument.getParser().withParserOptions(parserOptions);

try {
const res = parserDocument.loadString(content);

res.errors.all().forEach((e) => {
// channel logging
if (getConfigBladeParserLintDebug()) {
this.outputChannel.appendLine(`${'#'.repeat(10)} bladeParser\n`);
this.outputChannel.appendLine(`errorCode: ${e.errorCode}`);
this.outputChannel.appendLine(`level: ${e.level}`);
this.outputChannel.appendLine(`message: ${e.message}`);
this.outputChannel.appendLine(`startPosition: ${JSON.stringify(e.node?.startPosition)}`);
this.outputChannel.appendLine(`endPosition: ${JSON.stringify(e.node?.endPosition)}\n`);
}

const message = e.message;
const level = e.level;
const severity = this.convertBladeErrorLevelToDiagnosticsSeverity(level);
const errorCode = e.errorCode;

let startPosition: Position | undefined;
let endPosition: Position | undefined;
if (e.node && e.node.startPosition && e.node.endPosition) {
// MEMO: The startPostion.char of this parser is adjusted by -1.
startPosition = Position.create(e.node.startPosition.line - 1, e.node.startPosition.char - 1);
// MEMO: The endPostiion of this parser is not very suitable for the linter, so we use startPostiion.
endPosition = Position.create(e.node.startPosition.line - 1, e.node.startPosition.char);
}

if (startPosition && endPosition) {
const diagnostic: Diagnostic = {
source: 'bladeParser',
code: errorCode,
range: Range.create(startPosition, endPosition),
message,
severity,
relatedInformation: [],
};

diagnostics.push(diagnostic);
}
});
} catch (e) {
this.collection.set(textDocument.uri, null);
}

this.collection.set(textDocument.uri, diagnostics);
}

private convertBladeErrorLevelToDiagnosticsSeverity(level: number) {
switch (level) {
case 0:
return DiagnosticSeverity.Error;
case 1:
return DiagnosticSeverity.Warning;
default:
return DiagnosticSeverity.Error;
}
}
}
37 changes: 37 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ camelcase-css@^2.0.1:
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==

camelize@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
integrity sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==

chai@^4.3.6:
version "4.3.6"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c"
Expand All @@ -383,6 +388,11 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"

"charenc@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==

check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
Expand Down Expand Up @@ -471,6 +481,11 @@ cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"

"crypt@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==

cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
Expand Down Expand Up @@ -1652,6 +1667,14 @@ semver@^7.3.7:
dependencies:
lru-cache "^6.0.0"

sha1@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848"
integrity sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==
dependencies:
charenc ">= 0.0.1"
crypt ">= 0.0.1"

shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
Expand Down Expand Up @@ -1679,6 +1702,15 @@ source-map-js@^1.0.2:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==

stillat-blade-parser@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/stillat-blade-parser/-/stillat-blade-parser-0.1.0.tgz#e83adf7991467b6b1970dc103ee3e9da6812eaac"
integrity sha512-jr2GbkvNKUXqwFn93YgkAzs08eZhnD2fqjGnM0fG74GWDEVoupqz8yw2p6xQgseptqRt/kVr3W2Y+NVnWuCzqA==
dependencies:
camelize "^1.0.0"
sha1 "^1.1.1"
uuid "^8.3.2"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
Expand Down Expand Up @@ -1839,6 +1871,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==

uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
Expand Down