Skip to content
Permalink
Browse files

Fix: Update schema to draft-07

Also stop downloading the schema automatically after 24h to avoid
possible issues in the wild.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Rel #3413
  • Loading branch information
molant authored and sarvaje committed Dec 3, 2019
1 parent 48ef62e commit f7e9b83d60cf8f117fed9290705f7b5e585b4177
@@ -30,7 +30,7 @@ const tests: HintLocalTest[] = [
path: path.join(__dirname, 'fixtures', 'invalidschemaenum'),
reports: [
{
message: `'compilerOptions.lib[3]' should be equal to one of the allowed values 'es5, es6, es2015, es7, es2016, es2017, es2018, es2019, es2020, esnext, dom, dom.iterable, webworker, webworker.importscripts, scripthost, es2015.core, es2015.collection, es2015.generator, es2015.iterable, es2015.promise, es2015.proxy, es2015.reflect, es2015.symbol, es2015.symbol.wellknown, es2016.array.include, es2017.object, es2017.intl, es2017.sharedmemory, es2017.string, es2017.typedarrays, es2018.asynciterable, es2018.intl, es2018.promise, es2018.regexp, es2019.array, es2019.object, es2019.string, es2019.symbol, es2020.string, es2020.symbol.wellknown, esnext.asynciterable, esnext.array, esnext.bigint, esnext.intl, esnext.symbol'. Value found 'invalidlib'`,
message: `'compilerOptions.lib[3]' should be equal to one of the allowed values 'ES5, ES6, ES7, ES2015, ES2015.Collection, ES2015.Core, ES2015.Generator, ES2015.Iterable, ES2015.Promise, ES2015.Proxy, ES2015.Reflect, ES2015.Symbol.WellKnown, ES2015.Symbol, ES2016, ES2016.Array.Include, ES2017, ES2017.Intl, ES2017.Object, ES2017.SharedMemory, ES2017.String, ES2017.TypedArrays, ES2018, ES2018.AsyncIterable, ES2018.Intl, ES2018.Promise, ES2018.Regexp, ES2019, ES2019.Array, ES2019.Object, ES2019.String, ES2019.Symbol, ES2020, ES2020.String, ES2020.Symbol.WellKnown, ESNext, ESNext.Array, ESNext.AsyncIterable, ESNext.BigInt, ESNext.Intl, ESNext.Symbol, DOM, DOM.Iterable, ScriptHost, WebWorker, WebWorker.ImportScripts'. Value found '"invalidlib"'. Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]5|[Ee][Ss]6|[Ee][Ss]7$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2015(\\.([Cc][Oo][Ll][Ll][Ee][Cc][Tt][Ii][Oo][Nn]|[Cc][Oo][Rr][Ee]|[Gg][Ee][Nn][Ee][Rr][Aa][Tt][Oo][Rr]|[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]|[Pp][Rr][Oo][Mm][Ii][Ss][Ee]|[Pp][Rr][Oo][Xx][Yy]|[Rr][Ee][Ff][Ll][Ee][Cc][Tt]|[Ss][Yy][Mm][Bb][Oo][Ll].[Ww][Ee][Ll][Ll][Kk][Nn][Oo][Ww][Nn]|[Ss][Yy][Mm][Bb][Oo][Ll]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2016(\\.[Aa][Rr][Rr][Aa][Yy].[Ii][Nn][Cc][Ll][Uu][Dd][Ee])?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2017(\\.([Ii][Nn][Tt][Ll]|[Oo][Bb][Jj][Ee][Cc][Tt]|[Ss][Hh][Aa][Rr][Ee][Dd][Mm][Ee][Mm][Oo][Rr][Yy]|[Ss][Tt][Rr][Ii][Nn][Gg]|[Tt][Yy][Pp][Ee][Dd][Aa][Rr][Rr][Aa][Yy][Ss]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2018(\\.([Aa][Ss][Yy][Nn][Cc][Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]|[Ii][Nn][Tt][Ll]|[Pp][Rr][Oo][Mm][Ii][Ss][Ee]|[Rr][Ee][Gg][Ee][Xx][Pp]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2019(\\.([Aa][Rr][Rr][Aa][Yy]|[Oo][Bb][Jj][Ee][Cc][Tt]|[Ss][Tt][Rr][Ii][Nn][Gg]|[Ss][Yy][Mm][Bb][Oo][Ll]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss]2020(\\.([Ss][Tt][Rr][Ii][Nn][Gg]|[Ss][Yy][Mm][Bb][Oo][Ll].[Ww][Ee][Ll][Ll][Kk][Nn][Oo][Ww][Nn]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ee][Ss][Nn][Ee][Xx][Tt](\\.([Aa][Rr][Rr][Aa][Yy]|[Aa][Ss][Yy][Nn][Cc][Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]|[Bb][Ii][Gg][Ii][Nn][Tt]|[Ii][Nn][Tt][Ll]|[Ss][Yy][Mm][Bb][Oo][Ll]))?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Dd][Oo][Mm](\\.[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee])?$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ss][Cc][Rr][Ii][Pp][Tt][Hh][Oo][Ss][Tt]$'. Value found 'invalidlib' Or 'compilerOptions.lib[3]' should match pattern '^[Ww][Ee][Bb][Ww][Oo][Rr][Kk][Ee][Rr](\\.[Ii][Mm][Pp][Oo][Rr][Tt][Ss][Cc][Rr][Ii][Pp][Tt][Ss])?$'. Value found 'invalidlib'`,
position: { match: '"invalidlib"' },
severity: Severity.error
}
@@ -41,7 +41,7 @@ const tests: HintLocalTest[] = [
path: path.join(__dirname, 'fixtures', 'invalidschemapattern'),
reports: [
{
message: `'compilerOptions.target' should be equal to one of the allowed values 'es3, es5, es6, es2015, es2016, es2017, es2018, es2019, es2020, esnext'. Value found '"invalid"'. Or 'compilerOptions.target' should match pattern '^([eE][sS]([356]|(20(1[56789]|20))|[nN][eE][xX][tT]))$'. Value found 'invalid'`,
message: `'compilerOptions.target' should be equal to one of the allowed values 'ES3, ES5, ES6, ES2015, ES2016, ES2017, ES2018, ES2019, ES2020, ESNext'. Value found '"invalid"'. Or 'compilerOptions.target' should match pattern '^([Ee][Ss]([356]|(20(1[56789]|20))|[Nn][Ee][Xx][Tt]))$'. Value found 'invalid'`,
position: { match: 'target' },
severity: Severity.error
}
@@ -8,7 +8,6 @@
"timeout": "1m"
},
"dependencies": {
"@hint/utils-debug": "^1.0.0",
"@hint/utils-fs": "^1.0.0",
"@hint/utils-json": "^1.0.0",
"@hint/utils-network": "^1.0.0",
@@ -1,25 +1,17 @@
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';

import { cloneDeep } from 'lodash';

import { loadJSONFile, writeFileAsync } from '@hint/utils-fs';
import { requestAsync } from '@hint/utils-network';
import { loadJSONFile } from '@hint/utils-fs';
import { finalConfig, IJSONResult, parseJSON, SchemaValidationResult, validate } from '@hint/utils-json';
import { debug as d } from '@hint/utils-debug';
import { Engine, FetchEnd, Parser } from 'hint';

import { TypeScriptConfig, TypeScriptConfigEvents } from './types';

export * from './types';

const debug = d(__filename);
const oneDay = 3600000 * 24;

export default class TypeScriptConfigParser extends Parser<TypeScriptConfigEvents> {
private schema: any;
private schemaUpdated: boolean = false;
private schemaPath: string = path.join(__dirname, 'schema.json');

public constructor(engine: Engine<TypeScriptConfigEvents>) {
@@ -48,77 +40,6 @@ export default class TypeScriptConfigParser extends Parser<TypeScriptConfigEvent
return validationResult;
}

private compilerOptionsExists(schema: any) {
return schema.definitions &&
schema.definitions.compilerOptionsDefinition &&
schema.definitions.compilerOptionsDefinition.properties &&
schema.definitions.compilerOptionsDefinition.properties.compilerOptions;
}

private typeAcquisitionExists(schema: any) {
return schema.definitions &&
schema.definitions.typeAcquisitionDefinition &&
schema.definitions.typeAcquisitionDefinition.properties &&
schema.definitions.typeAcquisitionDefinition.properties.typeAcquisition;
}

private async getFileStat(file: string): Promise<fs.Stats | null> {
let stats: fs.Stats | null = null;

try {
stats = await promisify(fs.stat)(file);
} catch (e) {
debug('Error getting the schema file stats');
debug(e);
}

return stats;
}

private async downloadSchema(): Promise<any> {
let schema: any = null;

try {
schema = JSON.parse(await requestAsync('http://json.schemastore.org/tsconfig'));

} catch (e) {
debug('Error downloading the schema file');
debug(e);
}

return schema;
}

private async updateSchema(): Promise<void> {
const now = Date.now();

const schemaStat: fs.Stats | null = await this.getFileStat(this.schemaPath);

const modified: number = schemaStat ? new Date(schemaStat.mtime).getTime() : Date.now();

if (!schemaStat || (now - modified > oneDay)) {
debug('TypeScript Schema is older than 24h.');
debug('Updating TypeScript Schema');

const schema = await this.downloadSchema();

if (this.compilerOptionsExists(schema)) {
schema.definitions.compilerOptionsDefinition.properties.compilerOptions.additionalProperties = false;
}

if (this.typeAcquisitionExists(schema)) {
schema.definitions.typeAcquisitionDefinition.properties.typeAcquisition.additionalProperties = false;
}

this.schema = schema;

await writeFileAsync(this.schemaPath, JSON.stringify(schema, null, 2));

}

this.schemaUpdated = true;
}

private async parseTypeScript(fetchEnd: FetchEnd) {
const resource = fetchEnd.resource;
const fileName = path.basename(resource);
@@ -143,15 +64,11 @@ export default class TypeScriptConfigParser extends Parser<TypeScriptConfigEvent
let result: IJSONResult;

try {
if (!this.schemaUpdated) {
await this.updateSchema();
}

result = parseJSON(fetchEnd.response.body.content);

const originalConfig = cloneDeep(result.data);

const config = await finalConfig<TypeScriptConfig>(result.data, resource);
const config = finalConfig<TypeScriptConfig>(result.data, resource);

if (config instanceof Error) {
await this.engine.emitAsync(`parse::error::typescript-config::extends`,
@@ -1,6 +1,6 @@
{
"title": "JSON schema for the TypeScript compiler's configuration file",
"$schema": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-07/schema#",

"definitions": {
"filesDefinition": {
@@ -278,62 +278,3 @@ test('If we receive a json with an extends with an invalid json, it should emit
t.is(engineEmitAsyncSpy.callCount, 3);
t.is(engineEmitAsyncSpy.args[2][0], 'parse::error::typescript-config::extends');
});

test(`If the schema file was updated in less than 24 hours, it shouldn't update the current schema`, async (t) => {
const sandbox = t.context.sandbox;
const { engine, fs, requestAsyncStub, TypeScriptConfigParser, writeFileAsyncStub } = mockContext(t.context);

const fsStatStub = sandbox.stub(fs, 'stat').callsFake((path: string, callback) => {
callback(null, { mtime: new Date() });
});

new TypeScriptConfigParser(engine); // eslint-disable-line no-new

const validJSON = loadJSONFile(path.join(__dirname, 'fixtures', 'tsconfig.valid.json'));

await engine.emitAsync('fetch::end::json', {
resource: 'tsconfig.improved.json',
response: { body: { content: JSON.stringify(validJSON) } }
} as FetchEnd);

t.true(fsStatStub.calledOnce);
t.false(requestAsyncStub.called);
t.false(writeFileAsyncStub.called);
});

test(`If the schema file wasn't updated in less than 24 hours, it should update the current schema`, async (t) => {
const sandbox = t.context.sandbox;
const { engine, fs, requestAsyncStub, TypeScriptConfigParser, writeFileAsyncStub } = mockContext(t.context);

const today = new Date();
const dayBeforeYesterday = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 2);

const fsStatStub = sandbox.stub(fs, 'stat').callsFake((path: string, callback) => {
callback(null, { mtime: dayBeforeYesterday });
});

requestAsyncStub.resolves(schema);
writeFileAsyncStub.resolves();

new TypeScriptConfigParser(engine); // eslint-disable-line no-new

const validJSON = loadJSONFile(path.join(__dirname, 'fixtures', 'tsconfig.valid.json'));

await engine.emitAsync('fetch::end::json', {
resource: 'tsconfig.improved.json',
response: { body: { content: JSON.stringify(validJSON) } }
} as FetchEnd);

t.true(fsStatStub.calledOnce);
t.true(requestAsyncStub.calledOnce);
t.is(requestAsyncStub.args[0][0], 'http://json.schemastore.org/tsconfig');
t.true(writeFileAsyncStub.calledOnce);

const oldSchema = JSON.parse(schema);
const newSchema = JSON.parse(writeFileAsyncStub.args[0][1]);

t.is(typeof oldSchema.definitions.compilerOptionsDefinition.properties.compilerOptions.additionalProperties, 'undefined');
t.is(typeof oldSchema.definitions.typeAcquisitionDefinition.properties.typeAcquisition.additionalProperties, 'undefined');
t.false(newSchema.definitions.compilerOptionsDefinition.properties.compilerOptions.additionalProperties);
t.false(newSchema.definitions.typeAcquisitionDefinition.properties.typeAcquisition.additionalProperties);
});

0 comments on commit f7e9b83

Please sign in to comment.
You can’t perform that action at this time.