Skip to content
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
2 changes: 2 additions & 0 deletions packages/svelte-check/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"glob": "^7.1.6",
"import-fresh": "^3.2.1",
"minimist": "^1.2.5",
"sade": "^1.7.4",
"source-map": "^0.7.3",
"svelte-preprocess": "^4.0.0",
"typescript": "*"
Expand All @@ -45,6 +46,7 @@
"@tsconfig/node12": "^1.0.0",
"@types/glob": "^7.1.1",
"@types/minimist": "^1.2.0",
"@types/sade": "^1.7.2",
"rollup": "^2.28.0",
"rollup-plugin-cleanup": "^3.0.0",
"rollup-plugin-copy": "^3.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/svelte-check/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default [
// Dependencies of svelte-language-server
// we don't want to bundle and instead require them as dependencies
'typescript',
'sade',
'svelte',
'svelte/compiler',
'svelte-preprocess',
Expand Down
145 changes: 51 additions & 94 deletions packages/svelte-check/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,21 @@
* This code's groundwork is taken from https://github.com/vuejs/vetur/tree/master/vti
*/

import { watch } from 'chokidar';
import * as fs from 'fs';
import glob from 'glob';
import argv from 'minimist';
import * as path from 'path';
import { SvelteCheck, SvelteCheckOptions } from 'svelte-language-server';
import { SvelteCheck } from 'svelte-language-server';
import { Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-protocol';
import { URI } from 'vscode-uri';
import { parseOptions, SvelteCheckCliOptions } from './options';
import {
DEFAULT_FILTER,
DiagnosticFilter,
HumanFriendlyWriter,
MachineFriendlyWriter,
Writer,
DiagnosticFilter,
DEFAULT_FILTER
Writer
} from './writers';
import { watch } from 'chokidar';

const outputFormats = ['human', 'human-verbose', 'machine'] as const;
type OutputFormat = typeof outputFormats[number];

type Result = {
fileCount: number;
Expand Down Expand Up @@ -160,8 +157,8 @@ class DiagnosticsWatcher {
}
}

function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter {
switch (myArgs['threshold']) {
function createFilter(opts: SvelteCheckCliOptions): DiagnosticFilter {
switch (opts.threshold) {
case 'error':
return (d) => d.severity === DiagnosticSeverity.Error;
case 'warning':
Expand All @@ -173,96 +170,56 @@ function createFilter(myArgs: argv.ParsedArgs): DiagnosticFilter {
}
}

function instantiateWriter(myArgs: argv.ParsedArgs): Writer {
const outputFormat: OutputFormat = outputFormats.includes(myArgs['output'])
? myArgs['output']
: 'human-verbose';

const filter = createFilter(myArgs);
function instantiateWriter(opts: SvelteCheckCliOptions): Writer {
const filter = createFilter(opts);

if (outputFormat === 'human-verbose' || outputFormat === 'human') {
return new HumanFriendlyWriter(process.stdout, outputFormat === 'human-verbose', filter);
if (opts.outputFormat === 'human-verbose' || opts.outputFormat === 'human') {
return new HumanFriendlyWriter(
process.stdout,
opts.outputFormat === 'human-verbose',
filter
);
} else {
return new MachineFriendlyWriter(process.stdout, filter);
}
}

function getOptions(myArgs: argv.ParsedArgs, workspacePath: string): SvelteCheckOptions {
let tsconfig = myArgs['tsconfig'];
if (tsconfig && !path.isAbsolute(tsconfig)) {
tsconfig = path.join(workspacePath, tsconfig);
}

return {
compilerWarnings: stringToObj(myArgs['compiler-warnings']),
diagnosticSources: <any>(
myArgs['diagnostic-sources']?.split(',')?.map((s: string) => s.trim())
),
tsconfig
};

function stringToObj(str = '') {
return str
.split(',')
.map((s) => s.trim())
.filter((s) => !!s)
.reduce((settings, setting) => {
const [name, val] = setting.split(':');
if (val === 'error' || val === 'ignore') {
settings[name] = val;
}
return settings;
}, <Record<string, 'error' | 'ignore'>>{});
}
}

(async () => {
const myArgs = argv(process.argv.slice(1));
let workspaceUri;

let workspacePath = myArgs['workspace'];
if (workspacePath) {
if (!path.isAbsolute(workspacePath)) {
workspacePath = path.resolve(process.cwd(), workspacePath);
}
workspaceUri = URI.file(workspacePath);
} else {
workspaceUri = URI.file(process.cwd());
}

const writer = instantiateWriter(myArgs);

const svelteCheck = new SvelteCheck(
workspaceUri.fsPath,
getOptions(myArgs, workspaceUri.fsPath)
);
const filePathsToIgnore = myArgs['ignore']?.split(',') || [];

if (myArgs['watch']) {
new DiagnosticsWatcher(
workspaceUri,
svelteCheck,
writer,
filePathsToIgnore,
!!myArgs['tsconfig']
);
} else {
if (!myArgs['tsconfig']) {
await openAllDocuments(workspaceUri, filePathsToIgnore, svelteCheck);
}
const result = await getDiagnostics(workspaceUri, writer, svelteCheck);
if (
result &&
result.errorCount === 0 &&
(!myArgs['fail-on-warnings'] || result.warningCount === 0) &&
(!myArgs['fail-on-hints'] || result.hintCount === 0)
) {
process.exit(0);
parseOptions(async (opts) => {
try {
const writer = instantiateWriter(opts);

const svelteCheck = new SvelteCheck(opts.workspaceUri.fsPath, {
compilerWarnings: opts.compilerWarnings,
diagnosticSources: opts.diagnosticSources,
tsconfig: opts.tsconfig
});

if (opts.watch) {
new DiagnosticsWatcher(
opts.workspaceUri,
svelteCheck,
writer,
opts.filePathsToIgnore,
!!opts.tsconfig
);
} else {
process.exit(1);
if (!opts.tsconfig) {
await openAllDocuments(opts.workspaceUri, opts.filePathsToIgnore, svelteCheck);
}
const result = await getDiagnostics(opts.workspaceUri, writer, svelteCheck);
if (
result &&
result.errorCount === 0 &&
(!opts.failOnWarnings || result.warningCount === 0) &&
(!opts.failOnHints || result.hintCount === 0)
) {
process.exit(0);
} else {
process.exit(1);
}
}
} catch (_err) {
console.error(_err);
console.error('svelte-check failed');
}
})().catch((_err) => {
console.error(_err);
console.error('svelte-check failed');
});
152 changes: 152 additions & 0 deletions packages/svelte-check/src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import * as path from 'path';
import sade from 'sade';
import { URI } from 'vscode-uri';

export interface SvelteCheckCliOptions {
workspaceUri: URI;
outputFormat: OutputFormat;
watch: boolean;
tsconfig?: string;
filePathsToIgnore: string[];
failOnWarnings: boolean;
failOnHints: boolean;
compilerWarnings: Record<string, 'error' | 'ignore'>;
diagnosticSources: DiagnosticSource[];
threshold: Threshold;
}

// eslint-disable max-len
export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) {
const prog = sade('svelte-check', true)
.version('1.x')
.option(
'--workspace',
'Path to your workspace. All subdirectories except node_modules and those listed in `--ignore` are checked'
)
.option(
'--output',
'What output format to use. Options are human, human-verbose, machine.',
'human-verbose'
)
.option(
'--watch',
'Will not exit after one pass but keep watching files for changes and rerun diagnostics',
false
)
.option(
'--tsconfig',
'Pass a path to a tsconfig or jsconfig file. The path can be relative to the workspace path or absolute. Doing this means that only files matched by the files/include/exclude pattern of the config file are diagnosed. It also means that errors from TypeScript and JavaScript files are reported.'
)
.option(
'--ignore',
'Files/folders to ignore - relative to workspace root, comma-separated, inside quotes. Example: `--ignore "dist,build"`'
)
.option(
'--fail-on-warnings',
'Will also exit with error code when there are warnings',
false
)
.option('--fail-on-hints', 'Will also exit with error code when there are hints', false)
.option(
'--compiler-warnings',
'A list of Svelte compiler warning codes. Each entry defines whether that warning should be ignored or treated as an error. Warnings are comma-separated, between warning code and error level is a colon; all inside quotes. Example: `--compiler-warnings "css-unused-selector:ignore,unused-export-let:error"`'
)
.option(
'--diagnostic-sources',
'A list of diagnostic sources which should run diagnostics on your code. Possible values are `js` (includes TS), `svelte`, `css`. Comma-separated, inside quotes. By default all are active. Example: `--diagnostic-sources "js,svelte"`'
)
.option(
'--threshold',
'Filters the diagnostics to display. `error` will output only errors while `warning` will output warnings and errors.',
'hint'
)
.action((opts) => {
const workspaceUri = getWorkspaceUri(opts);
cb({
workspaceUri,
outputFormat: getOutputFormat(opts),
watch: !!opts.watch,
tsconfig: getTsconfig(opts, workspaceUri.fsPath),
filePathsToIgnore: getFilepathsToIgnore(opts),
failOnWarnings: !!opts['fail-on-warnings'],
failOnHints: !!opts['fail-on-hints'],
compilerWarnings: getCompilerWarnings(opts),
diagnosticSources: getDiagnosticSources(opts),
threshold: getThreshold(opts)
});
});

prog.parse(process.argv);
}
// eslint-enable max-len

const outputFormats = ['human', 'human-verbose', 'machine'] as const;
type OutputFormat = typeof outputFormats[number];

function getOutputFormat(opts: Record<string, any>): OutputFormat {
return outputFormats.includes(opts.output) ? opts.output : 'human-verbose';
}

function getWorkspaceUri(opts: Record<string, any>) {
let workspaceUri;
let workspacePath = opts.workspace;
if (workspacePath) {
if (!path.isAbsolute(workspacePath)) {
workspacePath = path.resolve(process.cwd(), workspacePath);
}
workspaceUri = URI.file(workspacePath);
} else {
workspaceUri = URI.file(process.cwd());
}
return workspaceUri;
}

function getTsconfig(myArgs: Record<string, any>, workspacePath: string) {
let tsconfig: string | undefined = myArgs.tsconfig;
if (tsconfig && !path.isAbsolute(tsconfig)) {
tsconfig = path.join(workspacePath, tsconfig);
}
return tsconfig;
}

function getCompilerWarnings(opts: Record<string, any>) {
return stringToObj(opts['compiler-warnings']);

function stringToObj(str = '') {
return str
.split(',')
.map((s) => s.trim())
.filter((s) => !!s)
.reduce((settings, setting) => {
const [name, val] = setting.split(':');
if (val === 'error' || val === 'ignore') {
settings[name] = val;
}
return settings;
}, <Record<string, 'error' | 'ignore'>>{});
}
}

const diagnosticSources = ['js', 'css', 'svelte'] as const;
type DiagnosticSource = typeof diagnosticSources[number];

function getDiagnosticSources(opts: Record<string, any>): DiagnosticSource[] {
const sources = opts['diagnostic-sources'];
return sources
? sources
.split(',')
?.map((s: string) => s.trim())
.filter((s: any) => diagnosticSources.includes(s))
: diagnosticSources;
}

function getFilepathsToIgnore(opts: Record<string, any>): string[] {
return opts.ignore?.split(',') || [];
}

const thresholds = ['hint', 'warning', 'error'] as const;
type Threshold = typeof thresholds[number];

function getThreshold(opts: Record<string, any>): Threshold {
return thresholds.includes(opts.threshold) ? opts.threshold : 'hint';
}