diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b2b273..bb009c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -247,6 +247,7 @@ this.createError( #### Adding to the core library * You should write a test for your rule. +* Add the rule's file, as well as its test file to tsconfig.json's `include` array, so that TypeScript can check for errors. * Add the rule to the list in `rules/index`, so that it is processed. #### Adding to your own application diff --git a/README.md b/README.md index 27bfbe3..3888114 100644 --- a/README.md +++ b/README.md @@ -229,10 +229,10 @@ To run tests locally, run: $ npm test ``` -The test run will also include a run of [eslint](https://eslint.org/). To run the tests without these, use: +The test run will also include a run of [eslint](https://eslint.org/) and [TypeScript](https://www.typescriptlang.org/). To run the tests without these, use: ```shell -$ npm run test-no-lint +$ npm run run-tests ``` ### Contributing diff --git a/package.json b/package.json index 30d6ce1..9837dd4 100644 --- a/package.json +++ b/package.json @@ -38,19 +38,23 @@ "write-file-atomic": "^3.0.3" }, "devDependencies": { + "@types/jasmine": "^5.1.4", + "@types/jasmine-expect": "^3.8.1", + "@types/node": "^20.11.24", "eslint": "^8.36.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-import": "^2.27.5", "jasmine": "^4.6.0", "nock": "^13.3.0", - "sync-request": "^6.1.0" + "sync-request": "^6.1.0", + "typescript": "^5.3.3" }, "scripts": { "lint": "eslint \"src/**/*.js\"", "lint-fix": "eslint \"src/**/*.js\" --fix", - "pretest": "npm run lint", - "test": "npm run test-no-lint", - "test-no-lint": "jasmine", + "pretest": "npm run lint && tsc", + "test": "npm run run-tests", + "run-tests": "jasmine", "test-debug": "node --inspect-brk -i ./node_modules/jasmine/bin/jasmine.js", "postpublish": "git push", "publish-patch": "npm test && git pull && git push && npm version patch && npm publish" diff --git a/src/classes/field.js b/src/classes/field.js index 776be3d..0a265b9 100644 --- a/src/classes/field.js +++ b/src/classes/field.js @@ -301,7 +301,9 @@ const Field = class { actualTypeKey = actualTypeKey.substr(1); } if ( + // @ts-expect-error typeof (this.constructor.canBeTypeOfMapping[testTypeKey]) !== 'undefined' + // @ts-expect-error && this.constructor.canBeTypeOfMapping[testTypeKey] === actualTypeKey ) { return true; diff --git a/src/classes/model-node.js b/src/classes/model-node.js index 618288c..73866c9 100644 --- a/src/classes/model-node.js +++ b/src/classes/model-node.js @@ -128,6 +128,7 @@ const ModelNode = class { // Does our property allow us to inherit? && typeof this.parentNode.model.fields[this.cleanName] !== 'undefined' && typeof this.parentNode.model.fields[this.cleanName].inheritsTo !== 'undefined' + // @ts-expect-error && this.constructor.checkInheritRule( this.parentNode.model.fields[this.cleanName].inheritsTo, field, @@ -142,6 +143,7 @@ const ModelNode = class { const modelField = this.model.fields[fieldKey]; const fieldValue = this.getValue(fieldKey); if (typeof modelField.inheritsFrom !== 'undefined' + // @ts-expect-error && this.constructor.checkInheritRule(modelField.inheritsFrom, field) && typeof fieldValue === 'object' && !(fieldValue instanceof Array) @@ -162,6 +164,7 @@ const ModelNode = class { } } if (parentModel) { + // @ts-expect-error const parentNode = new this.constructor( modelField.fieldName, fieldValue, diff --git a/src/classes/model.js b/src/classes/model.js index 490415d..596a224 100644 --- a/src/classes/model.js +++ b/src/classes/model.js @@ -80,6 +80,7 @@ const Model = class { } hasRequiredField(field) { + // @ts-expect-error return PropertyHelper.arrayHasField(this.requiredFields, field, this.version); } @@ -143,6 +144,7 @@ const Model = class { } hasRecommendedField(field) { + // @ts-expect-error return PropertyHelper.arrayHasField(this.recommendedFields, field, this.version); } diff --git a/src/helpers/json-loader.js b/src/helpers/json-loader.js index 44fd9fa..2f6e082 100644 --- a/src/helpers/json-loader.js +++ b/src/helpers/json-loader.js @@ -99,6 +99,7 @@ async function getFromFsCacheIfExists(baseCachePath, url) { // Probably just doesn't exist return { exists: false }; } + // @ts-expect-error const parsed = JSON.parse(rawCacheContents); return { exists: true, @@ -132,6 +133,7 @@ async function saveToFsCache(baseCachePath, url, fileObject) { async function getFromRemoteUrl(url) { let response; try { + // @ts-expect-error response = await axios.get(url, { headers: { 'Content-Type': 'application/ld+json', @@ -146,6 +148,7 @@ async function getFromRemoteUrl(url) { if (match !== null) { const { origin } = new URL(url); const linkUrl = match[1]; + // @ts-expect-error response = await axios.get(origin + linkUrl, { headers: { 'Content-Type': 'application/ld+json', @@ -217,7 +220,7 @@ async function getFileLoadRemote(url) { * * @param {string} url * @param {Object} options - * @returns {Object} + * @returns {Promise} */ async function getFileLoadRemoteAndCacheToFs(url, options) { { diff --git a/src/rules/rule.js b/src/rules/rule.js index 1daceb3..c1b1519 100644 --- a/src/rules/rule.js +++ b/src/rules/rule.js @@ -5,9 +5,26 @@ const ValidationError = require('../errors/validation-error'); class Rule { constructor(options) { this.options = options || new OptionsHelper(); + /** @type {string[] | '*'} */ this.targetModels = []; + /** @type {'*' | {[model: string]: '*' | string[]}} */ this.targetFields = {}; + /** @type {string[] | '*'} */ this.targetValidationModes = '*'; + /** + * @type {{ + * name: string; + * description: string; + * tests: {[key: string]: { + * description?: string; + * message: string; + * sampleValues?: { [messageTemplateArg: string]: string }; + * category: string; + * severity: string; + * type: string; + * }} + * }} + */ this.meta = { name: 'Rule', description: 'This is a base rule description that should be overridden.', @@ -38,11 +55,22 @@ class Rule { return errors; } - validateModel(/* node */) { + /** + * @param {import('../classes/model-node').ModelNodeType} node + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + validateModel(node) { throw Error('Model validation rule not implemented'); } - async validateField(/* node, field */) { + /** + * @param {import('../classes/model-node').ModelNodeType} node + * @param {string} field + * @returns {Promise} + */ + // eslint-disable-next-line no-unused-vars + async validateField(node, field) { throw Error('Field validation rule not implemented'); } @@ -88,6 +116,10 @@ class Rule { ); } + /** + * @param {import('../classes/model')} model + * @param {string} field + */ isFieldTargeted(model, field) { if (this.targetFields === '*') { return true; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a3b356a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "checkJs": true, + "downlevelIteration": true, + "target": "ES2019", + "moduleResolution": "node", + "types": ["jasmine", "node", "jasmine-expect"] + }, + "include": [ + "src/classes/**/*.js", + "src/errors/**/*.js", + "src/helpers/**/*.js", + "src/rules/rule.js", + // TODO add other rules + "src/exceptions.js" + ], + "exclude": [ + "src/helpers/graph.js" + ] +}