Merge pull request #3531 from Johannes-Andersen/jsonValidate

Add linting for JSON and workflow to test on push
main
Elbert Alias 4 years ago committed by GitHub
commit be9209d1df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,19 +2,18 @@ module.exports = {
root: true, root: true,
env: { env: {
browser: true, browser: true,
node: true node: true,
}, },
parserOptions: { parserOptions: {
parser: 'babel-eslint' parser: 'babel-eslint',
}, },
extends: [ extends: [
'@nuxtjs', '@nuxtjs',
'prettier', 'prettier',
'prettier/vue', 'prettier/vue',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:nuxt/recommended' 'plugin:nuxt/recommended',
], 'plugin:json/recommended',
plugins: [
'prettier'
], ],
plugins: ['prettier'],
} }

@ -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

@ -6,174 +6,167 @@ const { technologies, categories } = JSON.parse(
fs.readFileSync('./src/technologies.json') fs.readFileSync('./src/technologies.json')
) )
try { Object.keys(technologies).forEach((name) => {
Object.keys(technologies).forEach((name) => { const technology = technologies[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
if (groups > maxGroups) { // Validate regular expressions
throw new Error( ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach(
`Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})` (type) => {
) if (technology[type]) {
} const keyed =
typeof technology[type] === 'string' ||
Array.isArray(technology[type])
? { _: technology[type] }
: technology[type]
if (type === 'html' && !/[<>]/.test(regex)) { Object.keys(keyed).forEach((key) => {
throw new Error( const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]]
`HTML pattern must include < or >: ${regex} (${id})`
)
}
})
})
}
}
)
// Validate categories patterns.forEach((pattern, index) => {
technology.cats.forEach((id) => { const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
if (!categories[id]) {
throw new Error(`No such category: ${id} (${name})`)
}
})
// Validate icons const [regex, ...flags] = pattern.split('\\;')
if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
throw new Error(`No such icon: ${technology.icon} (${name})`)
}
// Validate website URLs let maxGroups = 0
try {
// eslint-disable-next-line no-new
const { protocol } = new URL(technology.website)
if (protocol !== 'http:' && protocol !== 'https:') { flags.forEach((flag) => {
throw new Error('Invalid protocol') const [key, value] = flag.split(':')
}
} catch (error) {
throw new Error(`Invalid website URL: ${technology.website} (${name})`)
}
// Validate implies and excludes if (key === 'version') {
const { implies, excludes } = technology const refs = value.match(/\\(\d+)/g)
if (implies) { if (refs) {
;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => { maxGroups = refs.reduce((max, ref) =>
const [_name, ...flags] = implied.split('\\;') 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]) { // Count capture groups
throw new Error(`Implied technology does not exist: ${_name} (${id})`) const groups = new RegExp(`${regex}|`).exec('').length - 1
}
flags.forEach((flag) => { if (groups > maxGroups) {
const [key, value] = flag.split(':') throw new Error(
`Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
)
}
if (key === 'confidence') { if (type === 'html' && !/[<>]/.test(regex)) {
if (
!/^\d+$/.test(value) ||
parseInt(value, 10) < 0 ||
parseInt(value, 10) > 99
) {
throw new Error( 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) { // Validate categories
;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => { technology.cats.forEach((id) => {
const id = `${name}: excludes[${excluded}]` if (!categories[id]) {
throw new Error(`No such category: ${id} (${name})`)
if (!technologies[excluded]) {
throw new Error(
`Excluded technology does not exist: ${excluded} (${id})`
)
}
})
} }
}) })
// Validate icons // Validate icons
fs.readdirSync(iconPath).forEach((file) => { if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
const filePath = `${iconPath}/${file}` 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 (!technologies[_name]) {
if (!/^(png|svg)$/i.test(file.split('.').pop())) { throw new Error(`Implied technology does not exist: ${_name} (${id})`)
throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
} }
if ( flags.forEach((flag) => {
!Object.values(technologies).some(({ icon }) => icon === file) && const [key, value] = flag.split(':')
file !== 'default.svg'
) { if (key === 'confidence') {
throw new Error(`Extraneous file: ${filePath}}`) 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) { if (
// eslint-disable-next-line no-console !Object.values(technologies).some(({ icon }) => icon === file) &&
console.error(error.message) file !== 'default.svg'
} ) {
throw new Error(`Extraneous file: ${filePath}}`)
}
}
})

@ -8,15 +8,17 @@
"@nuxtjs/eslint-config": "^3.1.0", "@nuxtjs/eslint-config": "^3.1.0",
"@nuxtjs/eslint-module": "^2.0.0", "@nuxtjs/eslint-module": "^2.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"eslint": "^7.7.0", "eslint": "^7.13.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.15.0",
"eslint-plugin-json": "^2.1.2",
"eslint-plugin-nuxt": "^1.0.0", "eslint-plugin-nuxt": "^1.0.0",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"prettier": "^2.1.0" "prettier": "^2.1.2"
}, },
"scripts": { "scripts": {
"link": "node ./bin/link.js", "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", "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", "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", "prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/technologies.json",

@ -93,6 +93,6 @@
"categoryName71": { "message": "Affiliate program" }, "categoryName71": { "message": "Affiliate program" },
"categoryName72": { "message": "Appointment scheduling" }, "categoryName72": { "message": "Appointment scheduling" },
"categoryName73": { "message": "Surveys" }, "categoryName73": { "message": "Surveys" },
"categoryName75": { "message": "A/B testing" }, "categoryName74": { "message": "A/B testing" },
"categoryName75": { "message": "Email" } "categoryName75": { "message": "Email" }
} }

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny" viewBox="0 0 51 51" overflow="scroll">
<path fill="#FF7800" d="M5.6 1.4h39.9c2.3 0 4.2 1.8 4.2 4.2v39.9c0 2.3-1.8 4.2-4.2 4.2H5.6c-2.3 0-4.2-1.8-4.2-4.2V5.6c0-2.3 1.9-4.2 4.2-4.2z"/>
<path fill="#FEFEFE" d="M40.1 17.6c-3.2 0-6.1 1.9-7.3 4.8-1-2.3-3.5-4.8-6.9-4.8h-.4c-3.5 0-6.1 1.9-7.3 4.8-1.3-2.9-4.2-4.8-7.3-4.8-4.4 0-7.9 3.5-7.9 7.9v8h4v-7.9c0-2.3 1.9-4.2 4.2-4.2s4.2 1.9 4.2 4.2v7.9h3.8v-3.2c3.2 4.4 10.7 4.4 13.6-1h-5.4c-.4.4-1 .4-1.5.4-1.5 0-3.2-1-3.8-2.9h10.7c.4 3.8 3.8 6.7 7.6 6.7 4.4 0 8.2-3.5 8.2-7.9s-4.1-8-8.5-8zM22 23.5c.6-1.5 2.3-2.4 3.8-2.3 1.5.1 2.5 1 3.2 2.3h-7zm18.1 6.1c-2.3 0-4.2-1.9-4.2-4.2s1.9-4.2 4.2-4.2c2.5 0 4.4 1.9 4.4 4.2s-1.9 4.2-4.4 4.2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 755 B

@ -1,33 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 230.39 230.39">
<defs>
<linearGradient id="circle" x1="173.17" y1="86.95" x2="141.86" y2="141.17" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#cc9300"/>
<stop offset="0.47" stop-color="#ea433a"/>
<stop offset="1" stop-color="#b327bf"/>
</linearGradient>
<linearGradient id="Square_2" data-name="Square 2" x1="92.49" y1="41" x2="67.07" y2="110.85" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#cc9300"/>
<stop offset="0.26" stop-color="#ea433a"/>
<stop offset="0.47" stop-color="#b327bf"/>
<stop offset="0.76" stop-color="#66f"/>
<stop offset="1" stop-color="#00bf9a"/>
</linearGradient>
<linearGradient id="triangle" x1="75.13" y1="190.31" x2="120.2" y2="143.64" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#b327bf"/>
<stop offset="0.47" stop-color="#66f"/>
<stop offset="1" stop-color="#00c09a"/>
</linearGradient>
</defs>
<path d="M154.49,220.39l-44.16,7c-30.12,4.77-41.53,3.36-53.48-.78a62.25,62.25,0,0,1-29.59-21.5C19.63,195,14.77,184.61,10,154.49L3,110.33C-1.77,80.21-.36,68.8,3.78,56.85a62.25,62.25,0,0,1,21.5-29.59C35.36,19.63,45.78,14.77,75.9,10l44.16-7c30.12-4.77,41.53-3.36,53.48.78a62.18,62.18,0,0,1,29.58,21.5c7.64,10.08,12.5,20.5,17.27,50.62l7,44.16c4.77,30.12,3.36,41.53-.78,53.48a62.18,62.18,0,0,1-21.5,29.58C195,210.76,184.61,215.62,154.49,220.39Z" style="fill-rule: evenodd"/>
<g id="Group-2-Copy-4">
<g id="Group-Copy-5">
<g id="Group-4-Copy-10">
<g id="Group-21">
<path id="Oval-Copy-84" d="M162.43,145.1a31.43,31.43,0,1,0-35.65-26.17A31.28,31.28,0,0,0,162.43,145.1Zm-1.57-9.94a21.37,21.37,0,1,1,17.45-24.39A21.2,21.2,0,0,1,160.86,135.16Z" style="fill: url(#circle)"/>
<path id="Rectangle-Copy-64" d="M61,107.94l46.64-7.38a5,5,0,0,0,4.18-5.76l-7.4-46.71a5,5,0,0,0-5.76-4.19L52,51.29a5,5,0,0,0-4.18,5.76l7.4,46.71A5,5,0,0,0,61,107.94Zm3.39-10.72L58.52,60.45l36.7-5.81L101,91.41Z" style="fill: url(#Square_2)"/>
<path id="Triangle-Copy-15" d="M90.56,124.91,70.33,181.56a5,5,0,0,0,5.53,6.67l56.94-9a5,5,0,0,0,3.2-8.05L99.29,123.53A5,5,0,0,0,90.56,124.91ZM96.94,137l25.91,33.62L82.66,177Z" style="fill: url(#triangle)"/>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

@ -13368,7 +13368,7 @@
"SMARTSTORE.VISITOR": "" "SMARTSTORE.VISITOR": ""
}, },
"html": "<!--Powered by SmartStore\\.NET - https://www\\.smartstore\\.com-->", "html": "<!--Powered by SmartStore\\.NET - https://www\\.smartstore\\.com-->",
"icon": "smartstore.png", "icon": "Smartstore.png",
"implies": "Microsoft ASP.NET", "implies": "Microsoft ASP.NET",
"meta": { "meta": {
"generator": "^SmartStore.NET (.+)$\\;version:\\1" "generator": "^SmartStore.NET (.+)$\\;version:\\1"

Loading…
Cancel
Save