Skip to content

Commit

Permalink
New: Node API
Browse files Browse the repository at this point in the history
  • Loading branch information
sarvaje committed Apr 4, 2019
1 parent d295945 commit cf98a31
Show file tree
Hide file tree
Showing 50 changed files with 1,522 additions and 1,001 deletions.
4 changes: 2 additions & 2 deletions packages/connector-jsdom/src/connector.ts
Expand Up @@ -41,8 +41,8 @@ import {
NetworkData
} from 'hint/dist/src/lib/types';
import { Engine } from 'hint/dist/src/lib/engine';
import createHTMLDocument from 'hint/dist/src/lib/utils/dom/create-html-document';
import traverse from 'hint/dist/src/lib/utils/dom/traverse';
import { createHTMLDocument } from 'hint/dist/src/lib/utils/dom/create-html-document';
import { traverse } from 'hint/dist/src/lib/utils/dom/traverse';

import { Requester } from '@hint/utils-connector-tools/dist/src/requester';

Expand Down
4 changes: 2 additions & 2 deletions packages/connector-jsdom/src/resource-loader.ts
Expand Up @@ -5,10 +5,10 @@ import { ResourceLoader } from 'jsdom';

import JSDOMConnector from './connector';
import { HTMLDocument } from 'hint/dist/src/lib/types';
import createHTMLDocument from 'hint/dist/src/lib/utils/dom/create-html-document';
import { createHTMLDocument } from 'hint/dist/src/lib/utils/dom/create-html-document';
import { NetworkData, FetchEnd, FetchError } from 'hint/dist/src/lib/types';
import { getContentTypeData, getType } from 'hint/dist/src/lib/utils/content-type';
import getElementByUrl from 'hint/dist/src/lib/utils/dom/get-element-by-url';
import { getElementByUrl } from 'hint/dist/src/lib/utils/dom/get-element-by-url';

const debug: debug.IDebugger = d(__filename);

Expand Down
28 changes: 26 additions & 2 deletions packages/connector-local/src/connector.ts
Expand Up @@ -5,6 +5,30 @@
* It currently only sends `fetch::end::*` events.
*/

/**
* `jsdom` always tries to load `canvas` even though it is not needed for
* the HTML parser. If there is a mismatch between the user's node version
* and where the HTML parser is being executed (e.g.: VS Code extension)
* `canvas` will fail to load and crash the excution. To avoid
* that we hijack `require`'s cache and set an empty `Module` for `canvas`
* and `canvas-prebuilt` so `jsdom` doesn't use it and continues executing
* normally.
*
*/

try {
const canvasPath = require.resolve('canvas');
const Module = require('module');
const fakeCanvas = new Module('', null);

/* istanbul ignore next */
fakeCanvas.exports = function () { };

require.cache[canvasPath] = fakeCanvas;
} catch (e) {
// `canvas` is not installed, nothing to do
}

/*
* ------------------------------------------------------------------------------
* Requirements
Expand All @@ -23,7 +47,7 @@ import globby from 'globby';
import { fs, logger, network } from '@hint/utils';

import { getContentTypeData, isTextMediaType, getType } from 'hint/dist/src/lib/utils/content-type';
import traverse from 'hint/dist/src/lib/utils/dom/traverse';
import { traverse } from 'hint/dist/src/lib/utils/dom/traverse';

const { cwd, isFile, readFileAsync } = fs;
const { asPathString, getAsUri } = network;
Expand Down Expand Up @@ -154,7 +178,7 @@ export default class LocalConnector implements IConnector {
const scanEndEvent: ScanEnd = { resource: href };

await this.engine.emitAsync('scan::end', scanEndEvent);
await this.engine.notify();
await this.engine.notify(href);

logger.log('Watching for file changes.');
}
Expand Down
2 changes: 1 addition & 1 deletion packages/create-hint/src/handlebars-utils.ts
@@ -1,7 +1,7 @@
import * as Handlebars from 'handlebars';

import { fs } from '@hint/utils';
import loadHintPackage from 'hint/dist/src/lib/utils/packages/load-hint-package';
import { loadHintPackage } from 'hint/dist/src/lib/utils/packages/load-hint-package';

const { readFileAsync } = fs;

Expand Down
2 changes: 1 addition & 1 deletion packages/create-parser/src/handlebars-utils.ts
@@ -1,7 +1,7 @@
import * as Handlebars from 'handlebars';

import { fs } from '@hint/utils';
import loadHintPackage from 'hint/dist/src/lib/utils/packages/load-hint-package';
import { loadHintPackage } from 'hint/dist/src/lib/utils/packages/load-hint-package';

const { readFileAsync } = fs;

Expand Down
6 changes: 3 additions & 3 deletions packages/extension-browser/src/content-script/connector.ts
Expand Up @@ -11,13 +11,13 @@ import {
HTMLDocument,
HTMLElement
} from 'hint/dist/src/lib/types';
import getElementByUrl from 'hint/dist/src/lib/utils/dom/get-element-by-url';
import { getElementByUrl } from 'hint/dist/src/lib/utils/dom/get-element-by-url';

import { Events } from '../shared/types';
import { eval } from '../shared/globals';
import { browser, document, location, window } from '../shared/globals';
import createHTMLDocument from 'hint/dist/src/lib/utils/dom/create-html-document';
import traverse from 'hint/dist/src/lib/utils/dom/traverse';
import { createHTMLDocument } from 'hint/dist/src/lib/utils/dom/create-html-document';
import { traverse } from 'hint/dist/src/lib/utils/dom/traverse';

export default class WebExtensionConnector implements IConnector {
private _document: HTMLDocument | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/extension-browser/src/content-script/webhint.ts
Expand Up @@ -3,7 +3,7 @@ require('util.promisify/shim')(); // Needed for `promisify` to work when bundled
import browserslist = require('browserslist'); // `require` used because `browserslist` exports a function.
import { URL } from 'url';

import { Engine } from 'hint';
import { Engine } from 'hint/dist/src/lib/engine';
import { Configuration } from 'hint/dist/src/lib/config';
import { HintResources, HintsConfigObject, IHintConstructor } from 'hint/dist/src/lib/types';

Expand Down
4 changes: 4 additions & 0 deletions packages/extension-vscode/CONTRIBUTING.md
Expand Up @@ -12,6 +12,10 @@ for Visual Studio Code.
* Select `Client + Server` from the drop down.
* Run the launch config.

NOTE: Make sure you open a file in the launched vscode instance
that webhint is registered for (like html or tsconfig.json), otherwise,
the server won't start.

## Running Tests

* Run `yarn test` from this directory.
Expand Down
60 changes: 24 additions & 36 deletions packages/extension-vscode/src/server.ts
Expand Up @@ -15,11 +15,8 @@ import {

import * as notifications from './notifications';

// TODO: Enhance `hint` exports so everything can be imported directly.
import * as hint from 'hint';
import * as config from 'hint/dist/src/lib/config';
import * as loader from 'hint/dist/src/lib/utils/resource-loader';
import { HintsConfigObject, Problem, Severity, UserConfig } from 'hint/dist/src/lib/types';
import { HintsConfigObject, Problem, Severity, UserConfig } from 'hint';

let workspace = '';

Expand Down Expand Up @@ -109,7 +106,7 @@ const loadModule = async <T>(context: string, name: string): Promise<T | null> =
};

// Load a user configuration, falling back to 'development' if none exists.
const loadUserConfig = (directory: string, Configuration: typeof config.Configuration): UserConfig => {
const loadUserConfig = (directory: string, Configuration: typeof hint.Configuration): UserConfig => {
const defaultConfig: UserConfig = { extends: ['development'] };

try {
Expand All @@ -122,37 +119,23 @@ const loadUserConfig = (directory: string, Configuration: typeof config.Configur
}
};

// Load a copy of webhint with the provided configuration.
const loadEngine = async (directory: string, configuration: config.Configuration): Promise<hint.Engine | null> => {
const localLoader = await loadModule<typeof loader>(directory, 'hint/dist/src/lib/utils/resource-loader');
const localHint = await loadModule<typeof hint>(directory, 'hint');

if (!localLoader || !localHint) {
return null;
}

const resources = localLoader.loadResources(configuration);

return new localHint.Engine(configuration, resources);
};

// Load both webhint and a configuration, adjusting it as needed for this extension.
const loadWebHint = async (directory: string): Promise<hint.Engine | null> => {
const configModule = await loadModule<typeof config>(directory, 'hint/dist/src/lib/config');
const loadWebHint = async (directory: string): Promise<hint.Analyzer | null> => {
const hintModule = await loadModule<typeof hint>(directory, 'hint');

// If no module was returned, the user cancelled installing webhint.
if (!configModule) {
if (!hintModule) {
return null;
}

const { Configuration } = configModule;
const { Configuration } = hintModule;
const userConfig = loadUserConfig(directory, Configuration);

// The vscode extension only works with the local connector.
userConfig.connector = { name: 'local' };

if (!userConfig.hints) {
userConfig.hints = { };
userConfig.hints = {};
}

/*
Expand All @@ -162,6 +145,11 @@ const loadWebHint = async (directory: string): Promise<hint.Engine | null> => {
*/
(userConfig.hints as HintsConfigObject)['http-compression'] = 'off';

/*
* Remove formatters because the extension doesn't use them.
*/
userConfig.formatters = [];

if (!userConfig.parsers) {
userConfig.parsers = [];
}
Expand All @@ -171,12 +159,12 @@ const loadWebHint = async (directory: string): Promise<hint.Engine | null> => {
userConfig.parsers.push('html');
}

let engine: hint.Engine | null = null;
let webhint: hint.Analyzer | null = null;

try {
engine = await loadEngine(directory, Configuration.fromConfig(userConfig));
webhint = hintModule.Analyzer.create(userConfig);

return engine;
return webhint;
} catch (e) {
// Instantiating webhint failed, log the error to the webhint output panel to aid debugging.
console.error(e);
Expand All @@ -198,7 +186,7 @@ const loadWebHint = async (directory: string): Promise<hint.Engine | null> => {
}
};

let engine: hint.Engine | null = null;
let webhint: hint.Analyzer | null = null;
let loaded = false;
let validating = false;
let validationQueue: TextDocument[] = [];
Expand Down Expand Up @@ -280,11 +268,11 @@ const validateTextDocument = async (textDocument: TextDocument): Promise<void> =
// Try to load webhint if this is the first validation.
if (!loaded) {
loaded = true;
engine = await loadWebHint(workspace);
webhint = await loadWebHint(workspace);
}

// Gracefully exit if all attempts to get an engine failed.
if (!engine) {
if (!webhint) {
return;
}

Expand All @@ -293,14 +281,14 @@ const validateTextDocument = async (textDocument: TextDocument): Promise<void> =

// Pass content directly to validate unsaved changes.
const content = textDocument.getText();
const problems = await engine.executeOn(url, { content });

// Clear problems to avoid duplicates since vscode remembers them for us.
engine.clear();
const AnalzyerResult = await webhint.analyze({
content,
url
});

// Send the computed diagnostics to VSCode.
connection.sendDiagnostics({
diagnostics: problems.map(problemToDiagnostic),
diagnostics: AnalzyerResult.length > 0 ? AnalzyerResult[0].problems.map(problemToDiagnostic) : [],
uri: textDocument.uri
});

Expand All @@ -316,7 +304,7 @@ const validateTextDocument = async (textDocument: TextDocument): Promise<void> =

// A watched .hintrc has changed. Reload the engine and re-validate documents.
connection.onDidChangeWatchedFiles(async () => {
engine = await loadWebHint(workspace);
webhint = await loadWebHint(workspace);
await Promise.all(documents.all().map((doc) => {
return validateTextDocument(doc);
}));
Expand Down
55 changes: 23 additions & 32 deletions packages/extension-vscode/tests/fixtures/mocks.ts
@@ -1,13 +1,11 @@
import * as url from 'url';

import {
InitializeParams,
InitializeResult,
TextDocument,
TextDocumentChangeEvent,
PublishDiagnosticsParams
} from 'vscode-languageserver';
import { Problem, IFetchOptions } from 'hint/dist/src/lib/types';
import { Endpoint, AnalyzerResult } from 'hint';

export type Message = {
title: string;
Expand All @@ -31,11 +29,6 @@ export type Connection = {
window: Window;
}

export type EngineType = {
clear: () => void;
executeOn: (target: url.URL, options?: IFetchOptions) => Partial<Problem>[];
}

export type Std = {
pipe: () => void;
};
Expand All @@ -55,6 +48,19 @@ export type FilesType = {
};

export const mocks = () => {
const analyzer = {
analyze(endpoints: Endpoint): Promise<AnalyzerResult[]> {
return Promise.resolve([]);
}
}

class Analyzer {
private constructor() { }
public static create() {
return analyzer;
}
}

const child = {
on(event: string, listener: () => void) {
if (event === 'exit') {
Expand All @@ -67,7 +73,6 @@ export const mocks = () => {
stdout: { pipe() { } }
};

// eslint-disable-next-line
const child_process = {
spawn(cmd: string) {
return child;
Expand All @@ -88,23 +93,13 @@ export const mocks = () => {
}
};

const engine = {
clear() { },
executeOn(target: url.URL, options?: IFetchOptions): Partial<Problem>[] {
return [];
}
};

const Configuration = {
fromConfig() { },
getFilenameForDirectory() {
return '';
},
loadConfigFile() { }
};

const loadResources = () => { };

let fileWatcher: () => any;
let initializer: (params: Partial<InitializeParams>) => Promise<InitializeResult>;

Expand Down Expand Up @@ -135,16 +130,8 @@ export const mocks = () => {
return connection;
};

class Engine {
public constructor() {
return engine;
}
}

const modules: { [name: string]: any } = {
hint: { Engine },
'hint/dist/src/lib/config': { Configuration },
'hint/dist/src/lib/utils/resource-loader': { loadResources }
hint: { Analyzer, Configuration }
};

const Files = {
Expand Down Expand Up @@ -176,17 +163,21 @@ export const mocks = () => {
}
};



return {
access,
/*
* 'as any' to avoid error:
* Exported variable 'mocks' has or is using private name 'Analyzer'.
*
* We need to export Analyzer to be able to stub 'Analyzer.create'.
*/
Analyzer: Analyzer as any,
analyzer,
child_process,
Configuration,
connection,
createConnection,
document,
documents,
engine,
Files,
fs,
getContentWatcher: () => {
Expand Down

0 comments on commit cf98a31

Please sign in to comment.