From cd68f007fe16281a4adb337cdf0dc7bb83c36874 Mon Sep 17 00:00:00 2001 From: Johannes Andersen Date: Sun, 15 Nov 2020 16:54:49 +0000 Subject: [PATCH 1/5] Add linting for JSON and workflow to test on push --- .eslintrc.js | 11 ++++---- .github/workflows/validate.yml | 25 +++++++++++++++++++ package.json | 10 +++++--- .../webextension/_locales/ca/messages.json | 2 +- .../images/icons/Plataforma NEO.svg | 4 --- 5 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/validate.yml delete mode 100644 src/drivers/webextension/images/icons/Plataforma NEO.svg diff --git a/.eslintrc.js b/.eslintrc.js index 8ff448efd..f91394cce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,19 +2,18 @@ module.exports = { root: true, env: { browser: true, - node: true + node: true, }, parserOptions: { - parser: 'babel-eslint' + parser: 'babel-eslint', }, extends: [ '@nuxtjs', 'prettier', 'prettier/vue', 'plugin:prettier/recommended', - 'plugin:nuxt/recommended' - ], - plugins: [ - 'prettier' + 'plugin:nuxt/recommended', + 'plugin:json/recommended', ], + plugins: ['prettier'], } diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..e9041ab8f --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,25 @@ +name: Validate +on: + push: + pull_request: + +jobs: + validate: + name: Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.3 + - uses: actions/setup-node@v2.1.2 + with: + node-version: '14' + - name: Restore npm cache + uses: actions/cache@v2 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Install dependencies + run: npm install + - name: Validate + run: npm run validate diff --git a/package.json b/package.json index c24bd1c32..6e6ff2e39 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,17 @@ "@nuxtjs/eslint-config": "^3.1.0", "@nuxtjs/eslint-module": "^2.0.0", "babel-eslint": "^10.1.0", - "eslint": "^7.7.0", - "eslint-config-prettier": "^6.11.0", + "eslint": "^7.13.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-json": "^2.1.2", "eslint-plugin-nuxt": "^1.0.0", "eslint-plugin-prettier": "^3.1.4", - "prettier": "^2.1.0" + "prettier": "^2.1.2" }, "scripts": { "link": "node ./bin/link.js", - "lint": "eslint --fix src/**/*.js", + "lint": "eslint src/**/*.{js,json}", + "lint:fix": "eslint --fix src/**/*.{js,json}", "validate": "yarn run lint && jsonlint -qV ./schema.json ./src/technologies.json && node ./bin/validate.js", "convert": "cd ./src/drivers/webextension/images/icons ; cp *.svg converted ; cd converted ; convert-svg-to-png *.svg --width 32 --height 32 ; rm *.svg", "prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/technologies.json", diff --git a/src/drivers/webextension/_locales/ca/messages.json b/src/drivers/webextension/_locales/ca/messages.json index 0ea5132c7..9df2f83aa 100644 --- a/src/drivers/webextension/_locales/ca/messages.json +++ b/src/drivers/webextension/_locales/ca/messages.json @@ -93,6 +93,6 @@ "categoryName71": { "message": "Affiliate program" }, "categoryName72": { "message": "Appointment scheduling" }, "categoryName73": { "message": "Surveys" }, - "categoryName75": { "message": "A/B testing" }, + "categoryName74": { "message": "A/B testing" }, "categoryName75": { "message": "Email" } } diff --git a/src/drivers/webextension/images/icons/Plataforma NEO.svg b/src/drivers/webextension/images/icons/Plataforma NEO.svg deleted file mode 100644 index 5a112f6e0..000000000 --- a/src/drivers/webextension/images/icons/Plataforma NEO.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From afbcc17ab9750694cf1a32c520d2cfede4b6c859 Mon Sep 17 00:00:00 2001 From: Johannes Andersen Date: Sun, 15 Nov 2020 16:59:33 +0000 Subject: [PATCH 2/5] Fix smartstore.png -> Smartstore.png --- src/technologies.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/technologies.json b/src/technologies.json index 567f70069..9f4f4ad34 100644 --- a/src/technologies.json +++ b/src/technologies.json @@ -13368,7 +13368,7 @@ "SMARTSTORE.VISITOR": "" }, "html": "", - "icon": "smartstore.png", + "icon": "Smartstore.png", "implies": "Microsoft ASP.NET", "meta": { "generator": "^SmartStore.NET (.+)$\\;version:\\1" From 3265a60a0dbd1d465e836380c6fb5cecd842f65a Mon Sep 17 00:00:00 2001 From: Johannes Andersen Date: Sun, 15 Nov 2020 17:06:36 +0000 Subject: [PATCH 3/5] Make script throw on fail --- bin/validate.js | 281 ++++++++++++++++++++---------------------- src/technologies.json | 2 +- 2 files changed, 138 insertions(+), 145 deletions(-) diff --git a/bin/validate.js b/bin/validate.js index cdf931123..7c9c9b238 100755 --- a/bin/validate.js +++ b/bin/validate.js @@ -6,174 +6,167 @@ const { technologies, categories } = JSON.parse( fs.readFileSync('./src/technologies.json') ) -try { - Object.keys(technologies).forEach((name) => { - const technology = technologies[name] - - // Validate regular expressions - ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach( - (type) => { - if (technology[type]) { - const keyed = - typeof technology[type] === 'string' || - Array.isArray(technology[type]) - ? { _: technology[type] } - : technology[type] - - Object.keys(keyed).forEach((key) => { - const patterns = Array.isArray(keyed[key]) - ? keyed[key] - : [keyed[key]] - - patterns.forEach((pattern, index) => { - const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]` - - const [regex, ...flags] = pattern.split('\\;') - - let maxGroups = 0 - - flags.forEach((flag) => { - const [key, value] = flag.split(':') - - if (key === 'version') { - const refs = value.match(/\\(\d+)/g) - - if (refs) { - maxGroups = refs.reduce((max, ref) => - Math.max(max, parseInt(refs[1] || 0)) - ) - } - } else if (key === 'confidence') { - if ( - !/^\d+$/.test(value) || - parseInt(value, 10) < 0 || - parseInt(value, 10) > 99 - ) { - throw new Error( - `Confidence value must a number between 0 and 99: ${value} (${id})` - ) - } - } else { - throw new Error(`Invalid flag: ${key} (${id})`) - } - }) - - // Validate regular expression - try { - // eslint-disable-next-line no-new - new RegExp(regex) - } catch (error) { - throw new Error(`${error.message} (${id})`) - } - - // Count capture groups - const groups = new RegExp(`${regex}|`).exec('').length - 1 +Object.keys(technologies).forEach((name) => { + const technology = technologies[name] - if (groups > maxGroups) { - throw new Error( - `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` - ) - } + // Validate regular expressions + ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach( + (type) => { + if (technology[type]) { + const keyed = + typeof technology[type] === 'string' || + Array.isArray(technology[type]) + ? { _: technology[type] } + : technology[type] - if (type === 'html' && !/[<>]/.test(regex)) { - throw new Error( - `HTML pattern must include < or >: ${regex} (${id})` - ) - } - }) - }) - } - } - ) + Object.keys(keyed).forEach((key) => { + const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]] - // Validate categories - technology.cats.forEach((id) => { - if (!categories[id]) { - throw new Error(`No such category: ${id} (${name})`) - } - }) + patterns.forEach((pattern, index) => { + const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]` - // Validate icons - if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) { - throw new Error(`No such icon: ${technology.icon} (${name})`) - } + const [regex, ...flags] = pattern.split('\\;') - // Validate website URLs - try { - // eslint-disable-next-line no-new - const { protocol } = new URL(technology.website) + let maxGroups = 0 - if (protocol !== 'http:' && protocol !== 'https:') { - throw new Error('Invalid protocol') - } - } catch (error) { - throw new Error(`Invalid website URL: ${technology.website} (${name})`) - } + flags.forEach((flag) => { + const [key, value] = flag.split(':') - // Validate implies and excludes - const { implies, excludes } = technology + if (key === 'version') { + const refs = value.match(/\\(\d+)/g) - if (implies) { - ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { - const [_name, ...flags] = implied.split('\\;') + if (refs) { + maxGroups = refs.reduce((max, ref) => + Math.max(max, parseInt(refs[1] || 0)) + ) + } + } else if (key === 'confidence') { + if ( + !/^\d+$/.test(value) || + parseInt(value, 10) < 0 || + parseInt(value, 10) > 99 + ) { + throw new Error( + `Confidence value must a number between 0 and 99: ${value} (${id})` + ) + } + } else { + throw new Error(`Invalid flag: ${key} (${id})`) + } + }) - const id = `${name}: implies[${implied}]` + // Validate regular expression + try { + // eslint-disable-next-line no-new + new RegExp(regex) + } catch (error) { + throw new Error(`${error.message} (${id})`) + } - if (!technologies[_name]) { - throw new Error(`Implied technology does not exist: ${_name} (${id})`) - } + // Count capture groups + const groups = new RegExp(`${regex}|`).exec('').length - 1 - flags.forEach((flag) => { - const [key, value] = flag.split(':') + if (groups > maxGroups) { + throw new Error( + `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` + ) + } - if (key === 'confidence') { - if ( - !/^\d+$/.test(value) || - parseInt(value, 10) < 0 || - parseInt(value, 10) > 99 - ) { + if (type === 'html' && !/[<>]/.test(regex)) { throw new Error( - `Confidence value must a number between 0 and 99: ${value} (${id})` + `HTML pattern must include < or >: ${regex} (${id})` ) } - } else { - throw new Error(`Invalid flag: ${key} (${id})`) - } + }) }) - }) + } } + ) - if (excludes) { - ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { - const id = `${name}: excludes[${excluded}]` - - if (!technologies[excluded]) { - throw new Error( - `Excluded technology does not exist: ${excluded} (${id})` - ) - } - }) + // Validate categories + technology.cats.forEach((id) => { + if (!categories[id]) { + throw new Error(`No such category: ${id} (${name})`) } }) // Validate icons - fs.readdirSync(iconPath).forEach((file) => { - const filePath = `${iconPath}/${file}` + if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) { + throw new Error(`No such icon: ${technology.icon} (${name})`) + } + + // Validate website URLs + try { + // eslint-disable-next-line no-new + const { protocol } = new URL(technology.website) + + if (protocol !== 'http:' && protocol !== 'https:') { + throw new Error('Invalid protocol') + } + } catch (error) { + throw new Error(`Invalid website URL: ${technology.website} (${name})`) + } + + // Validate implies and excludes + const { implies, excludes } = technology + + if (implies) { + ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { + const [_name, ...flags] = implied.split('\\;') + + const id = `${name}: implies[${implied}]` - if (fs.statSync(filePath).isFile() && !file.startsWith('.')) { - if (!/^(png|svg)$/i.test(file.split('.').pop())) { - throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`) + if (!technologies[_name]) { + throw new Error(`Implied technology does not exist: ${_name} (${id})`) } - if ( - !Object.values(technologies).some(({ icon }) => icon === file) && - file !== 'default.svg' - ) { - throw new Error(`Extraneous file: ${filePath}}`) + flags.forEach((flag) => { + const [key, value] = flag.split(':') + + if (key === 'confidence') { + if ( + !/^\d+$/.test(value) || + parseInt(value, 10) < 0 || + parseInt(value, 10) > 99 + ) { + throw new Error( + `Confidence value must a number between 0 and 99: ${value} (${id})` + ) + } + } else { + throw new Error(`Invalid flag: ${key} (${id})`) + } + }) + }) + } + + if (excludes) { + ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { + const id = `${name}: excludes[${excluded}]` + + if (!technologies[excluded]) { + throw new Error( + `Excluded technology does not exist: ${excluded} (${id})` + ) } + }) + } +}) + +// Validate icons +fs.readdirSync(iconPath).forEach((file) => { + const filePath = `${iconPath}/${file}` + + if (fs.statSync(filePath).isFile() && !file.startsWith('.')) { + if (!/^(png|svg)$/i.test(file.split('.').pop())) { + throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`) } - }) -} catch (error) { - // eslint-disable-next-line no-console - console.error(error.message) -} + + if ( + !Object.values(technologies).some(({ icon }) => icon === file) && + file !== 'default.svg' + ) { + throw new Error(`Extraneous file: ${filePath}}`) + } + } +}) diff --git a/src/technologies.json b/src/technologies.json index 9f4f4ad34..e594e492f 100644 --- a/src/technologies.json +++ b/src/technologies.json @@ -1513,7 +1513,7 @@ 1 ], "html": "<[^>]+data-apos-refreshable[^>]", - "icon": "ApostropheCMS.svg", + "icon": "apostrophecms.svg", "implies": "Node.js", "website": "http://apostrophecms.org" }, From e6d3aaf34ca1139ba45c812b28e53b0ca5690f34 Mon Sep 17 00:00:00 2001 From: Johannes Andersen Date: Sun, 15 Nov 2020 17:12:43 +0000 Subject: [PATCH 4/5] Fix ApostropheCMS.svg --- src/technologies.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/technologies.json b/src/technologies.json index e594e492f..9f4f4ad34 100644 --- a/src/technologies.json +++ b/src/technologies.json @@ -1513,7 +1513,7 @@ 1 ], "html": "<[^>]+data-apos-refreshable[^>]", - "icon": "apostrophecms.svg", + "icon": "ApostropheCMS.svg", "implies": "Node.js", "website": "http://apostrophecms.org" }, From 650809d8c62442f0a7b9afdf366e4075624bedf6 Mon Sep 17 00:00:00 2001 From: Johannes Andersen Date: Sun, 15 Nov 2020 17:14:57 +0000 Subject: [PATCH 5/5] Somehow I got two identical files --- .../images/icons/apostrophecms.svg | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/drivers/webextension/images/icons/apostrophecms.svg diff --git a/src/drivers/webextension/images/icons/apostrophecms.svg b/src/drivers/webextension/images/icons/apostrophecms.svg deleted file mode 100644 index 8ccef8d6c..000000000 --- a/src/drivers/webextension/images/icons/apostrophecms.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -