aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShin'ya Ueoka <ueokande@i-beam.org>2019-12-03 10:02:37 +0000
committerGitHub <noreply@github.com>2019-12-03 10:02:37 +0000
commit3c7230c3036e8bb2b2e9a752be9b0ef4a0a7349d (patch)
tree8c1bb889656f06ee6bc018ae053cf35e12613dfb
parentfeac179504a9276ad2e841702bf3fc1d89251679 (diff)
parent5205da572980f24d1632d0fa7df85124322ca960 (diff)
Merge pull request #684 from ueokande/jsonschema-settings
Parse settings by JSON Schema
-rw-r--r--.circleci/config.yml7
-rw-r--r--package-lock.json182
-rw-r--r--package.json5
-rw-r--r--src/shared/settings/Blacklist.ts47
-rw-r--r--src/shared/settings/Keymaps.ts22
-rw-r--r--src/shared/settings/Properties.ts18
-rw-r--r--src/shared/settings/Search.ts50
-rw-r--r--src/shared/settings/Settings.ts52
-rw-r--r--src/shared/settings/schema.json84
-rw-r--r--src/shared/settings/validate.js567
-rw-r--r--test/shared/settings/Blacklist.test.ts18
-rw-r--r--test/shared/settings/Keymaps.test.ts1
-rw-r--r--test/shared/settings/Search.test.ts13
-rw-r--r--tsconfig.json2
-rw-r--r--webpack.config.js2
15 files changed, 893 insertions, 177 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index fec66bd..8700c29 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -63,13 +63,6 @@ jobs:
- checkout
- setup_npm
- run: npm run lint
- - run:
- # NOTE: Karma loads ts-node automatically and treats karma.conf.js as a TypeScript.
- # Karma does not starts by karma.conf.js transpile failure, and this hack removes
- # ts-node module from the local before test.
- # See: https://github.com/karma-runner/karma/issues/3329
- name: Remove node-ts from node_modules
- command: mv node_modules/ts-node node_modules/ts-node.orig
- run: npm test
- run: npm run package
diff --git a/package-lock.json b/package-lock.json
index 4300fd2..b844719 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -581,9 +581,9 @@
"dev": true
},
"ajv": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz",
- "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==",
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+ "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
@@ -592,6 +592,28 @@
"uri-js": "^4.2.2"
}
},
+ "ajv-cli": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-3.0.0.tgz",
+ "integrity": "sha1-WCMjH2TigzBUEwaQsYCZYJ6YDyk=",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.0.0",
+ "ajv-pack": "^0.3.0",
+ "fast-json-patch": "^0.5.6",
+ "glob": "^7.0.3",
+ "json-schema-migrate": "^0.2.0",
+ "minimist": "^1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
"ajv-errors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz",
@@ -604,6 +626,16 @@
"integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
"dev": true
},
+ "ajv-pack": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/ajv-pack/-/ajv-pack-0.3.1.tgz",
+ "integrity": "sha1-tyxNQhnjko5ihC10Le2Tv1B5ZWA=",
+ "dev": true,
+ "requires": {
+ "js-beautify": "^1.6.4",
+ "require-from-string": "^1.2.0"
+ }
+ },
"amdefine": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
@@ -1576,6 +1608,12 @@
}
}
},
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@@ -1670,6 +1708,16 @@
"typedarray": "^0.0.6"
}
},
+ "config-chain": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
+ "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
+ "dev": true,
+ "requires": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
"connect": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
@@ -2226,6 +2274,36 @@
"safe-buffer": "^5.0.1"
}
},
+ "editorconfig": {
+ "version": "0.15.3",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
+ "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.19.0",
+ "lru-cache": "^4.1.5",
+ "semver": "^5.6.0",
+ "sigmund": "^1.0.1"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+ "dev": true,
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ }
+ }
+ },
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3077,6 +3155,12 @@
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
+ "fast-json-patch": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-0.5.7.tgz",
+ "integrity": "sha1-taj0nSWWJFlu+YuHLz/aiVtNhmU=",
+ "dev": true
+ },
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@@ -4946,6 +5030,45 @@
"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
"dev": true
},
+ "js-beautify": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.2.tgz",
+ "integrity": "sha512-ZtBYyNUYJIsBWERnQP0rPN9KjkrDfJcMjuVGcvXOUJrD1zmOGwhRwQ4msG+HJ+Ni/FA7+sRQEMYVzdTQDvnzvQ==",
+ "dev": true,
+ "requires": {
+ "config-chain": "^1.1.12",
+ "editorconfig": "^0.15.3",
+ "glob": "^7.1.3",
+ "mkdirp": "~0.5.1",
+ "nopt": "~4.0.1"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+ "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ }
+ }
+ },
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -4980,6 +5103,41 @@
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
},
+ "json-schema-migrate": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-0.2.0.tgz",
+ "integrity": "sha1-ukelsAcvxyOWRg4b1gtE1SF4u8Y=",
+ "dev": true,
+ "requires": {
+ "ajv": "^5.0.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
+ "dev": true
+ }
+ }
+ },
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -7357,6 +7515,12 @@
}
}
},
+ "proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=",
+ "dev": true
+ },
"proxy-addr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
@@ -7884,6 +8048,12 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
+ "require-from-string": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz",
+ "integrity": "sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg=",
+ "dev": true
+ },
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
@@ -8339,6 +8509,12 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
+ "sigmund": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
+ "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=",
+ "dev": true
+ },
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
diff --git a/package.json b/package.json
index 726e909..381bb7d 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,11 @@
"name": "vim-vixen",
"description": "Vim vixen",
"scripts": {
+ "schema": "ajv compile -s src/shared/settings/schema.json -o src/shared/settings/validate.js",
"start": "webpack --mode development -w --debug --devtool inline-source-map",
"build": "NODE_ENV=production webpack --mode production --progress --display-error-details --devtool inline-source-map",
"package": "npm run build && script/package",
- "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
+ "lint": "eslint --ext .ts,.tsx src",
"type-checks": "tsc --noEmit",
"test": "karma start",
"test:e2e": "mocha --timeout 10000 --retries 10 --require ts-node/register --extension ts e2e"
@@ -35,6 +36,8 @@
"@types/sinon": "^7.0.13",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
+ "ajv": "^6.10.2",
+ "ajv-cli": "^3.0.0",
"chai": "^4.2.0",
"css-loader": "^3.2.0",
"eslint": "^6.2.2",
diff --git a/src/shared/settings/Blacklist.ts b/src/shared/settings/Blacklist.ts
index 0cfbd71..1903a78 100644
--- a/src/shared/settings/Blacklist.ts
+++ b/src/shared/settings/Blacklist.ts
@@ -12,18 +12,6 @@ const regexFromWildcard = (pattern: string): RegExp => {
return new RegExp(regexStr);
};
-const isArrayOfString = (raw: any): boolean => {
- if (!Array.isArray(raw)) {
- return false;
- }
- for (let x of Array.from(raw)) {
- if (typeof x !== 'string') {
- return false;
- }
- }
- return true;
-};
-
export class BlacklistItem {
public readonly pattern: string;
@@ -47,30 +35,10 @@ export class BlacklistItem {
this.keyEntities = this.keys.map(Key.fromMapKey);
}
- static fromJSON(raw: any): BlacklistItem {
- if (typeof raw === 'string') {
- return new BlacklistItem(raw, false, []);
- } else if (typeof raw === 'object' && raw !== null) {
- if (!('url' in raw)) {
- throw new TypeError(
- `missing field "url" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (typeof raw.url !== 'string') {
- throw new TypeError(
- `invalid field "url" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (!('keys' in raw)) {
- throw new TypeError(
- `missing field "keys" of blacklist item: ${JSON.stringify(raw)}`);
- }
- if (!isArrayOfString(raw.keys)) {
- throw new TypeError(
- `invalid field "keys" of blacklist item: ${JSON.stringify(raw)}`);
- }
- return new BlacklistItem(raw.url as string, true, raw.keys as string[]);
- }
- throw new TypeError(
- `invalid format of blacklist item: ${JSON.stringify(raw)}`);
+ static fromJSON(json: BlacklistItemJSON): BlacklistItem {
+ return typeof json === 'string'
+ ? new BlacklistItem(json, false, [])
+ : new BlacklistItem(json.url, true, json.keys);
}
toJSON(): BlacklistItemJSON {
@@ -103,11 +71,8 @@ export default class Blacklist {
) {
}
- static fromJSON(json: any): Blacklist {
- if (!Array.isArray(json)) {
- throw new TypeError('blacklist is not an array: ' + JSON.stringify(json));
- }
- let items = Array.from(json).map(item => BlacklistItem.fromJSON(item));
+ static fromJSON(json: BlacklistJSON): Blacklist {
+ let items = json.map(o => BlacklistItem.fromJSON(o));
return new Blacklist(items);
}
diff --git a/src/shared/settings/Keymaps.ts b/src/shared/settings/Keymaps.ts
index a5558b0..5870313 100644
--- a/src/shared/settings/Keymaps.ts
+++ b/src/shared/settings/Keymaps.ts
@@ -1,23 +1,25 @@
import * as operations from '../operations';
-export type KeymapsJSON = { [key: string]: operations.Operation };
+type OperationJson = {
+ type: string
+} | {
+ type: string;
+ [prop: string]: string | number | boolean;
+};
+export type KeymapsJSON = { [key: string]: OperationJson };
export default class Keymaps {
constructor(
- private readonly data: KeymapsJSON,
+ private readonly data: { [key: string]: operations.Operation },
) {
}
- static fromJSON(json: any): Keymaps {
- if (typeof json !== 'object' || json === null) {
- throw new TypeError('invalid keymaps type: ' + JSON.stringify(json));
- }
-
- let data: KeymapsJSON = {};
+ static fromJSON(json: KeymapsJSON): Keymaps {
+ let entries: { [key: string]: operations.Operation } = {};
for (let key of Object.keys(json)) {
- data[key] = operations.valueOf(json[key]);
+ entries[key] = operations.valueOf(json[key]);
}
- return new Keymaps(data);
+ return new Keymaps(entries);
}
combine(other: Keymaps): Keymaps {
diff --git a/src/shared/settings/Properties.ts b/src/shared/settings/Properties.ts
index 63ff991..27fb62e 100644
--- a/src/shared/settings/Properties.ts
+++ b/src/shared/settings/Properties.ts
@@ -1,3 +1,4 @@
+
export type PropertiesJSON = {
hintchars?: string;
smoothscroll?: boolean;
@@ -65,22 +66,7 @@ export default class Properties {
this.complete = complete || defaultValues.complete;
}
- static fromJSON(json: any): Properties {
- let defNames: Set<string> = new Set(defs.map(def => def.name));
- let unknownName = Object.keys(json).find(name => !defNames.has(name));
- if (unknownName) {
- throw new TypeError(`Unknown property name: "${unknownName}"`);
- }
-
- for (let def of defs) {
- if (!Object.prototype.hasOwnProperty.call(json, def.name)) {
- continue;
- }
- if (typeof json[def.name] !== def.type) {
- throw new TypeError(
- `property "${def.name}" is not ${def.type}`);
- }
- }
+ static fromJSON(json: PropertiesJSON): Properties {
return new Properties(json);
}
diff --git a/src/shared/settings/Search.ts b/src/shared/settings/Search.ts
index 4580236..7d7e555 100644
--- a/src/shared/settings/Search.ts
+++ b/src/shared/settings/Search.ts
@@ -12,18 +12,10 @@ export default class Search {
) {
}
- static fromJSON(json: any): Search {
- let defaultEngine = Search.getStringField(json, 'default');
- let engines = Search.getObjectField(json, 'engines');
-
- for (let [name, url] of Object.entries(engines)) {
- if ((/\s/).test(name)) {
- throw new TypeError(
- `While space in the search engine not allowed: "${name}"`);
- }
- if (typeof url !== 'string') {
- throw new TypeError(
- `Invalid type of value in filed "engines": ${JSON.stringify(json)}`);
+ static fromJSON(json: SearchJSON): Search {
+ for (let [name, url] of Object.entries(json.engines)) {
+ if (!(/^[a-zA-Z0-9]+$/).test(name)) {
+ throw new TypeError('Search engine\'s name must be [a-zA-Z0-9]+');
}
let matches = url.match(/{}/g);
if (matches === null) {
@@ -32,15 +24,11 @@ export default class Search {
throw new TypeError(`Multiple {}-placeholders in URL of "${name}"`);
}
}
-
- if (!Object.keys(engines).includes(defaultEngine)) {
- throw new TypeError(`Default engine "${defaultEngine}" not found`);
+ if (!Object.keys(json.engines).includes(json.default)) {
+ throw new TypeError(`Default engine "${json.default}" not found`);
}
- return new Search(
- json.default as string,
- json.engines,
- );
+ return new Search(json.default, json.engines);
}
toJSON(): SearchJSON {
@@ -49,28 +37,4 @@ export default class Search {
engines: this.engines,
};
}
-
- private static getStringField(json: any, name: string): string {
- if (!Object.prototype.hasOwnProperty.call(json, name)) {
- throw new TypeError(
- `missing field "${name}" on search: ${JSON.stringify(json)}`);
- }
- if (typeof json[name] !== 'string') {
- throw new TypeError(
- `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
- }
- return json[name];
- }
-
- private static getObjectField(json: any, name: string): Object {
- if (!Object.prototype.hasOwnProperty.call(json, name)) {
- throw new TypeError(
- `missing field "${name}" on search: ${JSON.stringify(json)}`);
- }
- if (typeof json[name] !== 'object' || json[name] === null) {
- throw new TypeError(
- `invalid type of filed "${name}" on search: ${JSON.stringify(json)}`);
- }
- return json[name];
- }
}
diff --git a/src/shared/settings/Settings.ts b/src/shared/settings/Settings.ts
index 2c9e37f..97dda7f 100644
--- a/src/shared/settings/Settings.ts
+++ b/src/shared/settings/Settings.ts
@@ -1,13 +1,16 @@
+import Ajv from 'ajv';
+
import Keymaps, { KeymapsJSON } from './Keymaps';
import Search, { SearchJSON } from './Search';
import Properties, { PropertiesJSON } from './Properties';
import Blacklist, { BlacklistJSON } from './Blacklist';
+import validate from './validate';
export type SettingsJSON = {
- keymaps: KeymapsJSON,
- search: SearchJSON,
- properties: PropertiesJSON,
- blacklist: BlacklistJSON,
+ keymaps?: KeymapsJSON,
+ search?: SearchJSON,
+ properties?: PropertiesJSON,
+ blacklist?: BlacklistJSON,
};
export default class Settings {
@@ -36,25 +39,30 @@ export default class Settings {
this.blacklist = blacklist;
}
- static fromJSON(json: any): Settings {
+ static fromJSON(json: unknown): Settings {
+ let valid = validate(json);
+ if (!valid) {
+ let message = (validate as any).errors!!
+ .map((err: Ajv.ErrorObject) => {
+ return `'${err.dataPath}' ${err.message}`;
+ })
+ .join('; ');
+ throw new TypeError(message);
+ }
+
+ let obj = json as SettingsJSON;
let settings = { ...DefaultSetting };
- for (let key of Object.keys(json)) {
- switch (key) {
- case 'keymaps':
- settings.keymaps = Keymaps.fromJSON(json.keymaps);
- break;
- case 'search':
- settings.search = Search.fromJSON(json.search);
- break;
- case 'properties':
- settings.properties = Properties.fromJSON(json.properties);
- break;
- case 'blacklist':
- settings.blacklist = Blacklist.fromJSON(json.blacklist);
- break;
- default:
- throw new TypeError('unknown setting: ' + key);
- }
+ if (obj.keymaps) {
+ settings.keymaps = Keymaps.fromJSON(obj.keymaps);
+ }
+ if (obj.search) {
+ settings.search = Search.fromJSON(obj.search);
+ }
+ if (obj.properties) {
+ settings.properties = Properties.fromJSON(obj.properties);
+ }
+ if (obj.blacklist) {
+ settings.blacklist = Blacklist.fromJSON(obj.blacklist);
}
return new Settings(settings);
}
diff --git a/src/shared/settings/schema.json b/src/shared/settings/schema.json
new file mode 100644
index 0000000..31d47f1
--- /dev/null
+++ b/src/shared/settings/schema.json
@@ -0,0 +1,84 @@
+{
+ "type": "object",
+ "properties": {
+ "keymaps": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ }
+ }
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "default": {
+ "type": "string"
+ },
+ "engines": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": [
+ "default",
+ "engines"
+ ]
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "hintchars": {
+ "type": "string"
+ },
+ "smoothscroll": {
+ "type": "boolean"
+ },
+ "complete": {
+ "type": "string"
+ }
+ }
+ },
+ "blacklist": {
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "keys": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "url",
+ "keys"
+ ]
+ }
+ ]
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/src/shared/settings/validate.js b/src/shared/settings/validate.js
new file mode 100644
index 0000000..236488d
--- /dev/null
+++ b/src/shared/settings/validate.js
@@ -0,0 +1,567 @@
+'use strict';
+var validate = (function() {
+ var pattern0 = new RegExp('.*');
+ var refVal = [];
+ return function validate(data, dataPath, parentData, parentDataProperty, rootData) {
+ 'use strict';
+ var vErrors = null;
+ var errors = 0;
+ if ((data && typeof data === "object" && !Array.isArray(data))) {
+ var errs__0 = errors;
+ var valid1 = true;
+ for (var key0 in data) {
+ var isAdditional0 = !(false || key0 == 'keymaps' || key0 == 'search' || key0 == 'properties' || key0 == 'blacklist');
+ if (isAdditional0) {
+ valid1 = false;
+ validate.errors = [{
+ keyword: 'additionalProperties',
+ dataPath: (dataPath || '') + "",
+ schemaPath: '#/additionalProperties',
+ params: {
+ additionalProperty: '' + key0 + ''
+ },
+ message: 'should NOT have additional properties'
+ }];
+ return false;
+ break;
+ }
+ }
+ if (valid1) {
+ var data1 = data.keymaps;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ var errs__1 = errors;
+ var valid2 = true;
+ for (var key1 in data1) {
+ if (pattern0.test(key1)) {
+ var data2 = data1[key1];
+ var errs_2 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ if (true) {
+ var errs__2 = errors;
+ var valid3 = true;
+ if (data2.type === undefined) {
+ valid3 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/required',
+ params: {
+ missingProperty: 'type'
+ },
+ message: 'should have required property \'type\''
+ }];
+ return false;
+ } else {
+ var errs_3 = errors;
+ if (typeof data2.type !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\'].type',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/properties/type/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid3 = errors === errs_3;
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps[\'' + key1 + '\']',
+ schemaPath: '#/properties/keymaps/patternProperties/.*/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ if (!valid2) break;
+ } else valid2 = true;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.keymaps',
+ schemaPath: '#/properties/keymaps/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.search;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ if (true) {
+ var errs__1 = errors;
+ var valid2 = true;
+ if (data1.default === undefined) {
+ valid2 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/required',
+ params: {
+ missingProperty: 'default'
+ },
+ message: 'should have required property \'default\''
+ }];
+ return false;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.default !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.default',
+ schemaPath: '#/properties/search/properties/default/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ var data2 = data1.engines;
+ if (data2 === undefined) {
+ valid2 = false;
+ validate.errors = [{
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/required',
+ params: {
+ missingProperty: 'engines'
+ },
+ message: 'should have required property \'engines\''
+ }];
+ return false;
+ } else {
+ var errs_2 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ var errs__2 = errors;
+ var valid3 = true;
+ for (var key2 in data2) {
+ if (pattern0.test(key2)) {
+ var errs_3 = errors;
+ if (typeof data2[key2] !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.engines[\'' + key2 + '\']',
+ schemaPath: '#/properties/search/properties/engines/patternProperties/.*/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid3 = errors === errs_3;
+ if (!valid3) break;
+ } else valid3 = true;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search.engines',
+ schemaPath: '#/properties/search/properties/engines/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.search',
+ schemaPath: '#/properties/search/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.properties;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if ((data1 && typeof data1 === "object" && !Array.isArray(data1))) {
+ var errs__1 = errors;
+ var valid2 = true;
+ if (data1.hintchars === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.hintchars !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.hintchars',
+ schemaPath: '#/properties/properties/properties/hintchars/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ if (data1.smoothscroll === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.smoothscroll !== "boolean") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.smoothscroll',
+ schemaPath: '#/properties/properties/properties/smoothscroll/type',
+ params: {
+ type: 'boolean'
+ },
+ message: 'should be boolean'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ if (valid2) {
+ if (data1.complete === undefined) {
+ valid2 = true;
+ } else {
+ var errs_2 = errors;
+ if (typeof data1.complete !== "string") {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties.complete',
+ schemaPath: '#/properties/properties/properties/complete/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ }];
+ return false;
+ }
+ var valid2 = errors === errs_2;
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.properties',
+ schemaPath: '#/properties/properties/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ if (valid1) {
+ var data1 = data.blacklist;
+ if (data1 === undefined) {
+ valid1 = true;
+ } else {
+ var errs_1 = errors;
+ if (Array.isArray(data1)) {
+ var errs__1 = errors;
+ var valid1;
+ for (var i1 = 0; i1 < data1.length; i1++) {
+ var data2 = data1[i1];
+ var errs_2 = errors;
+ var errs__2 = errors;
+ var valid2 = false;
+ var errs_3 = errors;
+ if (typeof data2 !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/0/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid3 = errors === errs_3;
+ valid2 = valid2 || valid3;
+ if (!valid2) {
+ var errs_3 = errors;
+ if ((data2 && typeof data2 === "object" && !Array.isArray(data2))) {
+ if (true) {
+ var errs__3 = errors;
+ var valid4 = true;
+ if (data2.url === undefined) {
+ valid4 = false;
+ var err = {
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/required',
+ params: {
+ missingProperty: 'url'
+ },
+ message: 'should have required property \'url\''
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ } else {
+ var errs_4 = errors;
+ if (typeof data2.url !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].url',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/url/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid4 = errors === errs_4;
+ }
+ if (valid4) {
+ var data3 = data2.keys;
+ if (data3 === undefined) {
+ valid4 = false;
+ var err = {
+ keyword: 'required',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/required',
+ params: {
+ missingProperty: 'keys'
+ },
+ message: 'should have required property \'keys\''
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ } else {
+ var errs_4 = errors;
+ if (Array.isArray(data3)) {
+ var errs__4 = errors;
+ var valid4;
+ for (var i4 = 0; i4 < data3.length; i4++) {
+ var errs_5 = errors;
+ if (typeof data3[i4] !== "string") {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys[' + i4 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/items/type',
+ params: {
+ type: 'string'
+ },
+ message: 'should be string'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid5 = errors === errs_5;
+ if (!valid5) break;
+ }
+ } else {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + '].keys',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/properties/keys/type',
+ params: {
+ type: 'array'
+ },
+ message: 'should be array'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid4 = errors === errs_4;
+ }
+ }
+ }
+ } else {
+ var err = {
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf/1/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ }
+ var valid3 = errors === errs_3;
+ valid2 = valid2 || valid3;
+ }
+ if (!valid2) {
+ var err = {
+ keyword: 'anyOf',
+ dataPath: (dataPath || '') + '.blacklist[' + i1 + ']',
+ schemaPath: '#/properties/blacklist/items/anyOf',
+ params: {},
+ message: 'should match some schema in anyOf'
+ };
+ if (vErrors === null) vErrors = [err];
+ else vErrors.push(err);
+ errors++;
+ validate.errors = vErrors;
+ return false;
+ } else {
+ errors = errs__2;
+ if (vErrors !== null) {
+ if (errs__2) vErrors.length = errs__2;
+ else vErrors = null;
+ }
+ }
+ var valid2 = errors === errs_2;
+ if (!valid2) break;
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + '.blacklist',
+ schemaPath: '#/properties/blacklist/type',
+ params: {
+ type: 'array'
+ },
+ message: 'should be array'
+ }];
+ return false;
+ }
+ var valid1 = errors === errs_1;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ validate.errors = [{
+ keyword: 'type',
+ dataPath: (dataPath || '') + "",
+ schemaPath: '#/type',
+ params: {
+ type: 'object'
+ },
+ message: 'should be object'
+ }];
+ return false;
+ }
+ validate.errors = vErrors;
+ return errors === 0;
+ };
+})();
+validate.schema = {
+ "type": "object",
+ "properties": {
+ "keymaps": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string"
+ }
+ },
+ "required": ["type"]
+ }
+ }
+ },
+ "search": {
+ "type": "object",
+ "properties": {
+ "default": {
+ "type": "string"
+ },
+ "engines": {
+ "type": "object",
+ "patternProperties": {
+ ".*": {
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": ["default", "engines"]
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "hintchars": {
+ "type": "string"
+ },
+ "smoothscroll": {
+ "type": "boolean"
+ },
+ "complete": {
+ "type": "string"
+ }
+ }
+ },
+ "blacklist": {
+ "type": "array",
+ "items": {
+ "anyOf": [{
+ "type": "string"
+ }, {
+ "type": "object",
+ "properties": {
+ "url": {
+ "type": "string"
+ },
+ "keys": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "required": ["url", "keys"]
+ }]
+ }
+ }
+ },
+ "additionalProperties": false
+};
+validate.errors = null;
+module.exports = validate; \ No newline at end of file
diff --git a/test/shared/settings/Blacklist.test.ts b/test/shared/settings/Blacklist.test.ts
index 133112c..0112757 100644
--- a/test/shared/settings/Blacklist.test.ts
+++ b/test/shared/settings/Blacklist.test.ts
@@ -16,16 +16,6 @@ describe('BlacklistItem', () => {
expect(item.partial).to.be.true;
expect(item.keys).to.deep.equal(['j', 'k']);
});
-
- it('throws a TypeError', () => {
- expect(() => BlacklistItem.fromJSON(null)).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON(100)).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON({})).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON({url: 'google.com'})).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON({keys: ['a']})).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON({url: 'google.com', keys: 10})).to.throw(TypeError);
- expect(() => BlacklistItem.fromJSON({url: 'google.com', keys: ['a', 'b', 3]})).to.throw(TypeError);
- });
});
describe('#matches', () => {
@@ -118,14 +108,6 @@ describe('Blacklist', () => {
let blacklist = Blacklist.fromJSON([]);
expect(blacklist.toJSON()).to.deep.equals([]);
});
-
- it('throws a TypeError', () => {
- expect(() => Blacklist.fromJSON(null)).to.throw(TypeError);
- expect(() => Blacklist.fromJSON(100)).to.throw(TypeError);
- expect(() => Blacklist.fromJSON({})).to.throw(TypeError);
- expect(() => Blacklist.fromJSON([100])).to.throw(TypeError);
- expect(() => Blacklist.fromJSON([{}])).to.throw(TypeError);
- })
});
describe('#includesEntireBlacklist', () => {
diff --git a/test/shared/settings/Keymaps.test.ts b/test/shared/settings/Keymaps.test.ts
index 7896a63..9e4109f 100644
--- a/test/shared/settings/Keymaps.test.ts
+++ b/test/shared/settings/Keymaps.test.ts
@@ -19,7 +19,6 @@ describe('Keymaps', () => {
});
it('throws a TypeError by invalid settings', () => {
- expect(() => Keymaps.fromJSON(null)).to.throw(TypeError);
expect(() => Keymaps.fromJSON({
k: { type: "invalid.operation" },
})).to.throw(TypeError);
diff --git a/test/shared/settings/Search.test.ts b/test/shared/settings/Search.test.ts
index 7c9134d..51cd3eb 100644
--- a/test/shared/settings/Search.test.ts
+++ b/test/shared/settings/Search.test.ts
@@ -26,19 +26,6 @@ describe('Search', () => {
});
it('throws a TypeError by invalid settings', () => {
- expect(() => Search.fromJSON(null)).to.throw(TypeError);
- expect(() => Search.fromJSON({})).to.throw(TypeError);
- expect(() => Search.fromJSON([])).to.throw(TypeError);
- expect(() => Search.fromJSON({
- default: 123,
- engines: {}
- })).to.throw(TypeError);
- expect(() => Search.fromJSON({
- default: 'google',
- engines: {
- 'google': 123456,
- }
- })).to.throw(TypeError);
expect(() => Search.fromJSON({
default: 'wikipedia',
engines: {
diff --git a/tsconfig.json b/tsconfig.json
index 9f90223..f3a6a33 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
"module": "commonjs",
"lib": ["es6", "dom", "es2017"],
"allowJs": true,
- "checkJs": true,
+ "checkJs": false,
"jsx": "react",
"sourceMap": true,
"outDir": "./build",
diff --git a/webpack.config.js b/webpack.config.js
index 1cd05df..ee252bf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,7 +4,7 @@ const path = require('path');
const src = path.resolve(__dirname, 'src');
const dist = path.resolve(__dirname, 'build');
-config = {
+const config = {
entry: {
content: path.join(src, 'content'),
settings: path.join(src, 'settings'),