diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ee4e4e4174..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - node: true - }, - overrides: [ - ], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module' - }, - rules: { - eqeqeq: 'off', - quotes: ['error', 'single' , { 'allowTemplateLiterals': true }] - } -} \ No newline at end of file diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml new file mode 100644 index 0000000000..7d30697a21 --- /dev/null +++ b/.github/workflows/eslint.yml @@ -0,0 +1,24 @@ +name: ESLint + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + eslint: + name: Run ESLint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Run ESLint + run: npx eslint . --max-warnings=0 \ No newline at end of file diff --git a/DEVELOPING.md b/DEVELOPING.md index 712065ab46..0fc0e5cf94 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -36,6 +36,23 @@ ESBuild must also be used to compile the CSS supporting the MAPP and MAPP.UI ele npx esbuild --bundle public/css/_ui.css --outfile=public/css/ui.css --loader:.svg=dataurl +## ESLint + +The codebase makes use of the [eslint](eslint.org) package to ensure that our code adhere to different rules and coding guidelines. +To run `eslint` you will need to have the development packages installed. You can ensure they are installed by running `npm install` in the root of the xyz directory. + +To run the lint you can execute `npx eslint .` in the root of the application. This will show any issues there are with the codebase. You can also add the flag `--fix` to the command to allow eslint to fix any issues it may find. + +eslint command + + npx esbuild . + +eslint command with fix + + npx esbuild . --fix + +There are other extensions you can use in your editor to get on the fly error highlighting where any rules are broken. Please look into what eslint supports in your environment. + ## version.js hash The mapp module object holds a hash of the latest release commit which can be generated by executing the version.js script in the root. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..d5cb190633 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,22 @@ +export default [ + { + ignores: ['public/js/lib/*', 'docs/**/*'], + }, + { + files: ['**/*.js', '**/*.mjs'], + rules: { + quotes: ['error', 'single', { 'allowTemplateLiterals': true }], + 'prefer-const': ['error', { + 'destructuring': 'any', + 'ignoreReadBeforeAssign': true + }], + 'max-depth': ['error', + { + 'max': 4 + } + ], + // 'complexity': ['error', { 'max': 15 }], + 'no-nested-ternary': 'error' + } + } +]; \ No newline at end of file diff --git a/lib/layer/decorate.mjs b/lib/layer/decorate.mjs index 977a69c619..0dee92429d 100644 --- a/lib/layer/decorate.mjs +++ b/lib/layer/decorate.mjs @@ -236,7 +236,7 @@ async function zoomToExtent(params) { // Zooms to a specific extent. // XMLHttpRequest to layer extent endpoint - let response = await mapp.utils.xhr(`${this.mapview.host}/api/query/layer_extent?` + + const response = await mapp.utils.xhr(`${this.mapview.host}/api/query/layer_extent?` + mapp.utils.paramString({ // build query string for the url locale: this.mapview.locale.key, layer: this.key, diff --git a/lib/layer/featureFields.mjs b/lib/layer/featureFields.mjs index 866af75856..abac8a6c14 100644 --- a/lib/layer/featureFields.mjs +++ b/lib/layer/featureFields.mjs @@ -84,9 +84,9 @@ The jenks distribution method requires the stats.jenks utility method to calcula @param {layer} layer A decorated mapp layer object. */ function jenks(layer) { - let theme = layer.style.theme; + const theme = layer.style.theme; - let n = Math.min(layer.featureFields[theme.field].values.length, theme.categories.length); + const n = Math.min(layer.featureFields[theme.field].values.length, theme.categories.length); // Parse array values as float. layer.featureFields[theme.field].values = layer.featureFields[theme.field].values.map(parseFloat); @@ -113,7 +113,7 @@ The count distribution method counts values in the `featureFields.values[]` arra @param {layer} layer A decorated mapp layer object. */ function count(layer) { - let theme = layer.style.theme; + const theme = layer.style.theme; layer.featureFields[theme.field].values.forEach(val => { diff --git a/lib/layer/featureStyle.mjs b/lib/layer/featureStyle.mjs index 4901fb5ed4..96ab042d2a 100644 --- a/lib/layer/featureStyle.mjs +++ b/lib/layer/featureStyle.mjs @@ -144,7 +144,7 @@ export default function featureStyle(layer) { if (!layer.style.cluster) return; - let clusterScale = parseFloat(layer.style.cluster.clusterScale) + const clusterScale = parseFloat(layer.style.cluster.clusterScale) // Spread cluster style into feature.style. feature.style = { diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index 429992eafe..5653916356 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -265,10 +265,10 @@ function changeEndLoad(layer) { const bounds = layer.mapview.getBounds() // Assign current viewport if queryparam is truthy. - let viewport = [bounds.west, bounds.south, bounds.east, bounds.north, layer.mapview.srid]; + const viewport = [bounds.west, bounds.south, bounds.east, bounds.north, layer.mapview.srid]; // Assign current viewport if queryparam is truthy. - let z = layer.mapview.Map.getView().getZoom(); + const z = layer.mapview.Map.getView().getZoom(); layer.xhr = new XMLHttpRequest() diff --git a/lib/layer/themes/distributed.mjs b/lib/layer/themes/distributed.mjs index ffcf28376c..1c04b91893 100644 --- a/lib/layer/themes/distributed.mjs +++ b/lib/layer/themes/distributed.mjs @@ -25,7 +25,7 @@ export default function(theme, feature) { theme.index = 0 } - let field = theme.field || 'id' + const field = theme.field || 'id' // Get feature identifier for theme. const val = feature.properties[field] diff --git a/lib/layer/themes/graduated.mjs b/lib/layer/themes/graduated.mjs index 7c55464c8d..a87b555607 100644 --- a/lib/layer/themes/graduated.mjs +++ b/lib/layer/themes/graduated.mjs @@ -20,7 +20,7 @@ export default function (theme, feature) { // The graduated theme requires feature.properties. if (!feature.properties) return; - let catValue = Array.isArray(feature.properties.features) ? + const catValue = Array.isArray(feature.properties.features) ? // Reduce array of features to sum catValue feature.properties.features.reduce((total, F) => total + Number(F.getProperties()[theme.field]), 0) : @@ -35,9 +35,9 @@ export default function (theme, feature) { 'greater_than': val => cat => val >= cat.value } - let index = theme.categories.findIndex(graduated_breaks[theme.graduated_breaks](catValue)) + const index = theme.categories.findIndex(graduated_breaks[theme.graduated_breaks](catValue)) - let cat = theme.categories.at(index) + const cat = theme.categories.at(index) // Spread cat style to retain scale property feature.style = { diff --git a/lib/location/create.mjs b/lib/location/create.mjs index e08d784c71..3c660cda2b 100644 --- a/lib/location/create.mjs +++ b/lib/location/create.mjs @@ -80,7 +80,7 @@ export default async function createLocation(feature, interaction, layer) { setTimeout(checkFeature, 1000); function checkFeature() { - let found = layer.features?.find(F => F.properties?.id === location.id); + const found = layer.features?.find(F => F.properties?.id === location.id); if (found) { layer.source.un('tileloadend', concatFeatures); } else { diff --git a/lib/mapp.mjs b/lib/mapp.mjs index ee87300e1b..5672f7e261 100644 --- a/lib/mapp.mjs +++ b/lib/mapp.mjs @@ -57,7 +57,7 @@ if (window.ol === undefined) { } else { - let olVersion = parseFloat(ol?.util.VERSION) + const olVersion = parseFloat(ol?.util.VERSION) console.log(`OpenLayers version ${olVersion}`) diff --git a/lib/plugins/login.mjs b/lib/plugins/login.mjs index 46c242174d..71fad68643 100644 --- a/lib/plugins/login.mjs +++ b/lib/plugins/login.mjs @@ -25,6 +25,6 @@ export function login(plugin, mapview) { btnColumn.appendChild(mapp.utils.html.node` + href=${mapp.user ? '?logout=true' : '?login=true'}>
`); } \ No newline at end of file diff --git a/lib/ui/Gazetteer.mjs b/lib/ui/Gazetteer.mjs index 0821770836..0922a327ac 100644 --- a/lib/ui/Gazetteer.mjs +++ b/lib/ui/Gazetteer.mjs @@ -42,7 +42,7 @@ export default gazetteer => { if (!e.target.value.length) return; // Get possible coordinates from input. - let ll = e.target.value.split(',').map(parseFloat) + const ll = e.target.value.split(',').map(parseFloat) // Check whether coordinates are valid float values. if (ll.length === 2 && ll.every(n => typeof n === 'number' && !isNaN(n) && isFinite(n))) { diff --git a/lib/ui/elements/legendIcon.mjs b/lib/ui/elements/legendIcon.mjs index 95e9436691..d101be39ee 100644 --- a/lib/ui/elements/legendIcon.mjs +++ b/lib/ui/elements/legendIcon.mjs @@ -72,7 +72,7 @@ function createIconFromArray(style) { }); }; - let legendScale = style.icon[0].legendScale || 1; + const legendScale = style.icon[0].legendScale || 1; style.icon.forEach((icon) => { diff --git a/lib/ui/layers/filters.mjs b/lib/ui/layers/filters.mjs index e9cf14a227..4f6b43ec5f 100644 --- a/lib/ui/layers/filters.mjs +++ b/lib/ui/layers/filters.mjs @@ -522,7 +522,7 @@ async function filter_in(layer, filter) { const pattern = e.target.value; - let filtered = filter[filter.type].filter(val => + const filtered = filter[filter.type].filter(val => // val may not be string. val.toString().toLowerCase().startsWith(pattern.toLowerCase())) diff --git a/lib/ui/layers/legends/categorized.mjs b/lib/ui/layers/legends/categorized.mjs index 743ebeb285..e01ba357c6 100644 --- a/lib/ui/layers/legends/categorized.mjs +++ b/lib/ui/layers/legends/categorized.mjs @@ -85,7 +85,7 @@ export default function categorizedTheme(layer) { const cat_label = cat.label + (cat.count? ` [${cat.count}]`:'') // Cat label with filter function. - let label = mapp.utils.html`
catToggle(e, layer, cat)}>${cat_label}` diff --git a/lib/ui/locations/entries/cloudinary.mjs b/lib/ui/locations/entries/cloudinary.mjs index 4283b9adc7..578f869802 100644 --- a/lib/ui/locations/entries/cloudinary.mjs +++ b/lib/ui/locations/entries/cloudinary.mjs @@ -186,10 +186,10 @@ function imageLoad(e, entry) { img.onload = async () => { - let - canvas = mapp.utils.html.node``, - max_size = 1024, - width = img.width, + const canvas = mapp.utils.html.node`` + const max_size = 1024 + + let width = img.width, height = img.height // resize @@ -276,7 +276,7 @@ async function docLoad(e, entry) { async function trash(e, entry) { - const confirm = await mapp.ui.elements.confirm({text: mapp.dictionary.remove_item_confirm}); + const confirm = await mapp.ui.elements.confirm({ text: mapp.dictionary.remove_item_confirm }); if (!confirm) return; diff --git a/lib/utils/copyToClipboard.mjs b/lib/utils/copyToClipboard.mjs index 9bac15f02e..cfd1a17fb0 100644 --- a/lib/utils/copyToClipboard.mjs +++ b/lib/utils/copyToClipboard.mjs @@ -7,7 +7,7 @@ // Create temporary textarea to copy string to clipboard. export function copyToClipboard(str) { - let textArea = document.body.appendChild(mapp.utils.html.node` + const textArea = document.body.appendChild(mapp.utils.html.node`