Permalink
Browse files

Fix: Allow to pass content directly to things such as local connector

This change is done in order to allow things such as extensions
(e.g. VSCode extension).

Close #1268
  • Loading branch information...
antross authored and alrra committed Aug 22, 2018
1 parent 6a4cac4 commit 5d85d15e45c3df365cc8582e8d86df32ae8527d4
@@ -31,6 +31,7 @@ import * as logger from 'hint/dist/src/lib/utils/logging';
import {
IConnector,
IFetchOptions,
Event, FetchEnd, ScanEnd, NetworkData
} from 'hint/dist/src/lib/types';
import { Engine } from 'hint/dist/src/lib/engine';
@@ -84,7 +85,7 @@ export default class LocalConnector implements IConnector {
return [pattern];
}
private async fetch(target: string) {
private async fetch(target: string, options?: IFetchOptions) {
/*
* target can have one of these forms:
* - /path/to/file
@@ -97,7 +98,7 @@ export default class LocalConnector implements IConnector {
*/
const uri: url.URL = getAsUri(target);
const filePath: string = asPathString(uri);
const content: NetworkData = await this.fetchContent(filePath);
const content: NetworkData = await this.fetchContent(filePath, null, options);
const event: FetchEnd = {
element: null,
request: content.request,
@@ -248,8 +249,8 @@ export default class LocalConnector implements IConnector {
* ------------------------------------------------------------------------------
*/
public async fetchContent(filePath: string): Promise<NetworkData> {
const rawContent: Buffer = await readFileAsBuffer(filePath);
public async fetchContent(filePath: string, headers?: object, options?: IFetchOptions): Promise<NetworkData> {
const rawContent: Buffer = options && options.content ? Buffer.from(options.content) : await readFileAsBuffer(filePath);
const contentType = getContentTypeData(null, filePath, null, rawContent);
let content = '';
@@ -279,15 +280,15 @@ export default class LocalConnector implements IConnector {
};
}
public async collect(target: url.URL) {
public async collect(target: url.URL, options?: IFetchOptions) {
/** The target in string format */
const href: string = this._href = target.href;
const initialEvent: Event = { resource: href };
this.engine.emitAsync('scan::start', initialEvent);
const pathString = asPathString(target);
let files;
let files: string[];
if (isFile(pathString)) {
await this.engine.emitAsync('fetch::start::target', initialEvent);
@@ -300,9 +301,16 @@ export default class LocalConnector implements IConnector {
dot: true,
gitignore: true
} as any));
// Ignore options.content when matching multiple files
if (options && options.content) {
options.content = null;
}
}
await Promise.all(files.map(this.fetch.bind(this)));
await Promise.all(files.map((file) => {
return this.fetch(file, options);
}));
if (this._options.watch) {
await this.watch(pathString);
@@ -92,6 +92,28 @@ test.serial(`If target is a file (text), 'content' is setted`, async (t) => {
sandbox.restore();
});
test.serial(`If content is passed, it is used instead of the file`, async (t) => {
const testContent = '"Test Content";';
const fileUri = getAsUri(path.join(__dirname, 'fixtures', 'no-watch', 'script.js'));
const sandbox = sinon.createSandbox();
sandbox.stub(t.context.isFile, 'default').returns(true);
sandbox.spy(t.context.engine, 'emitAsync');
const connector = new LocalConnector(t.context.engine as any, {});
await connector.collect(fileUri, { content: testContent });
const event = t.context.engine.emitAsync.args[2][1];
t.is(typeof event.response.body.content, 'string');
t.is(event.response.body.content, testContent);
sandbox.restore();
});
test.serial(`If target is a file (image), 'content' is empty`, async (t) => {
const fileUri = getAsUri(path.join(__dirname, 'fixtures', 'no-watch', 'stylish-output.png'));
@@ -159,6 +181,38 @@ test.serial(`If target is a directory, shouldn't emit the event 'fetch::start::t
sandbox.restore();
});
test.serial(`If target is a directory, passed content should be ignored`, async (t) => {
const directoryUri = getAsUri(path.join(__dirname, 'fixtures', 'no-watch'));
const testContent = 'Test Content';
const sandbox = sinon.createSandbox();
sandbox.stub(t.context.isFile, 'default').returns(false);
sandbox.spy(t.context.engine, 'emitAsync');
const connector = new LocalConnector(t.context.engine as any, {});
await connector.collect(directoryUri, { content: testContent });
t.is(t.context.engine.emitAsync.callCount, 5);
const events: Array<Array<any>> = t.context.engine.emitAsync.args.map((args: Array<any>) => {
return args;
}).sort();
t.is(events[0][0], 'fetch::end::html');
t.not(events[0][1].response.body.content, testContent);
t.is(events[1][0], 'fetch::end::image');
t.not(events[1][1].response.body.content, testContent);
t.is(events[2][0], 'fetch::end::script');
t.not(events[2][1].response.body.content, testContent);
t.is(events[3][0], 'scan::end');
t.is(events[4][0], 'scan::start');
sandbox.restore();
});
test.serial(`If watch is true, it should watch the right files`, async (t) => {
const directoryUri = getAsUri(path.join(__dirname, 'fixtures', 'watch-no-ignore'));
const directory = asPathString(directoryUri);
@@ -17,7 +17,7 @@ import { remove } from 'lodash';
import { debug as d } from './utils/debug';
import { getSeverity } from './config/config-hints';
import { IAsyncHTMLElement, IConnector, NetworkData, UserConfig, Event, Problem, ProblemLocation, IHint, HintConfig, Severity, IHintConstructor, IConnectorConstructor, Parser, IFormatter, HintResources } from './types';
import { IAsyncHTMLElement, IConnector, IFetchOptions, NetworkData, UserConfig, Event, Problem, ProblemLocation, IHint, HintConfig, Severity, IHintConstructor, IConnectorConstructor, Parser, IFormatter, HintResources } from './types';
import * as logger from './utils/logging';
import { HintContext } from './hint-context';
import { HintScope } from './enums/hintscope';
@@ -281,13 +281,13 @@ export class Engine extends EventEmitter {
}
/** Runs all the configured hints on a target */
public async executeOn(target: url.URL): Promise<Array<Problem>> {
public async executeOn(target: url.URL, options?: IFetchOptions): Promise<Array<Problem>> {
const start: number = Date.now();
debug(`Starting the analysis on ${target.href}`);
await this.connector.collect(target);
await this.connector.collect(target, options);
debug(`Total runtime ${Date.now() - start}`);
@@ -17,17 +17,23 @@ export interface IConnector {
/** The headers from the response if applicable. */
headers?: object;
/** Collects all the information for the given target. */
collect(target: url.URL): Promise<any>;
collect(target: url.URL, options?: IFetchOptions): Promise<any>;
/** Releases any used resource and/or browser. */
close(): Promise<void>;
/** Download an external resource using ` customHeaders` if needed. */
fetchContent?(target: url.URL | string, customHeaders?: object): Promise<NetworkData>;
fetchContent?(target: url.URL | string, customHeaders?: object, options?: IFetchOptions): Promise<NetworkData>;
/** Evaluates the given JavaScript `code` asynchronously in the target. */
evaluate?(code: string): Promise<any>;
/** Finds all the nodes that match the given query. */
querySelectorAll?(query: string): Promise<Array<IAsyncHTMLElement>>;
}
/** Additional detail for calls to `connect` and `fetchContent` on `IConnector`. */
export interface IFetchOptions {
/** The content to analyze. Overrides fetching content from the provided target. */
content?: string;
}
export type BrowserInfo = {
isNew?: boolean;
pid: number;
@@ -19,7 +19,7 @@ eventEmitter.EventEmitter2.prototype.emitAsync = () => {
proxyquire('../../src/lib/engine', { eventemitter2: eventEmitter });
import { Engine } from '../../src/lib/engine';
import { HintResources, IFormatter, IConnector, IHint, HintMetadata, Problem } from '../../src/lib/types';
import { HintResources, IFormatter, IConnector, IFetchOptions, IHint, HintMetadata, Problem } from '../../src/lib/types';
import { Category } from '../../src/lib/enums/category';
class FakeConnector implements IConnector {
@@ -835,7 +835,7 @@ test.serial('If connector.collect fails, it should return an error', async (t) =
}
});
test.serial('executeOn should return all messages', async (t) => {
test.serial(`'executeOn' should return all messages`, async (t) => {
class FakeConnectorCollect implements IConnector {
private config;
public constructor(server: Engine, config: object) {
@@ -874,3 +874,46 @@ test.serial('executeOn should return all messages', async (t) => {
t.is(result.length, 2);
});
test.serial('executeOn should forward content if provided', async (t) => {
class FakeConnectorCollect implements IConnector {
private config;
private server: Engine;
public constructor(server: Engine, config: object) {
this.config = config;
this.server = server;
}
public collect(target: url.URL, options?: IFetchOptions) {
this.server.report('1', Category.other, 1, 'node', { column: 1, line: 1 }, options && options.content, target.href);
return Promise.resolve(target);
}
public close() {
return Promise.resolve();
}
}
const testContent = 'Test Content';
const engineObject = new Engine({
connector: {
name: 'myconnector',
options: {}
}
} as Configuration, {
connector: FakeConnectorCollect,
formatters: [],
hints: [],
incompatible: [],
missing: [],
parsers: []
});
const localUrl = new url.URL('http://localhost/');
const result = await engineObject.executeOn(localUrl, { content: testContent });
t.is(result[0].message, testContent);
});

0 comments on commit 5d85d15

Please sign in to comment.