From 58cb5a0b80409efccad9b223abbae35ad58d0f96 Mon Sep 17 00:00:00 2001 From: Martin Raifer Date: Mon, 17 Mar 2025 13:31:26 +0100 Subject: [PATCH] also check preset aliases in suspicious names validation see #9522 --- CHANGELOG.md | 1 + modules/validations/suspicious_name.js | 19 +++++---- test/spec/validations/suspicious_name.js | 53 ++++++++++++++---------- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b35a4379..879bc0e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ _Breaking developer changes, which may affect downstream projects or sites that #### :camera: Street-Level * Add prev/next button to viewer for local georeferenced photos ([#10852], thanks [@0xatulpatil]) #### :white_check_mark: Validation +* The Suspicious Names validator warning now also compares the Name field to the preset’s aliases (in addition to the preset’s name) in the user’s language #### :bug: Bugfixes * Fix some direction cones not appearing on railway tracks ([#10843], thanks [@k-yle]) * Better handling of rate limited API calls and other API errors ([#10299]) diff --git a/modules/validations/suspicious_name.js b/modules/validations/suspicious_name.js index 0a9bcd09d..ba071377e 100644 --- a/modules/validations/suspicious_name.js +++ b/modules/validations/suspicious_name.js @@ -45,17 +45,18 @@ export function validationSuspiciousName(context) { return false; } - /** @param {string} name @param {string} presetName */ - function nameMatchesPresetName(name, presetName) { - if (!presetName) return false; + /** @param {string} name */ + function nameMatchesPresetName(name, preset) { + if (!preset) return false; - return name.toLowerCase() === presetName.toLowerCase(); + name = name.toLowerCase(); + return name === preset.name().toLowerCase() || preset.aliases().some(alias => name === alias.toLowerCase()); } - /** @param {string} name @param {string} presetName */ - function isGenericName(name, tags, presetName) { + /** @param {string} name */ + function isGenericName(name, tags, preset) { name = name.toLowerCase(); - return nameMatchesRawTag(name, tags) || nameMatchesPresetName(name, presetName) || isGenericMatchInNsi(tags); + return nameMatchesRawTag(name, tags) || nameMatchesPresetName(name, preset) || isGenericMatchInNsi(tags); } function makeGenericNameIssue(entityId, nameKey, genericName, langCode) { @@ -113,7 +114,7 @@ export function validationSuspiciousName(context) { let issues = []; - const presetName = presetManager.match(entity, context.graph()).name(); + const preset = presetManager.match(entity, context.graph()); for (let key in tags) { const m = key.match(/^name(?:(?::)([a-zA-Z_-]+))?$/); @@ -122,7 +123,7 @@ export function validationSuspiciousName(context) { const langCode = m.length >= 2 ? m[1] : null; const value = tags[key]; - if (isGenericName(value, tags, presetName)) { + if (isGenericName(value, tags, preset)) { issues.provisional = _waitingForNsi; // retry later if we are waiting on NSI to finish loading issues.push(makeGenericNameIssue(entity.id, key, value, langCode)); } diff --git a/test/spec/validations/suspicious_name.js b/test/spec/validations/suspicious_name.js index 532f5a542..e2243f1af 100644 --- a/test/spec/validations/suspicious_name.js +++ b/test/spec/validations/suspicious_name.js @@ -34,7 +34,7 @@ describe('iD.validations.suspicious_name', function () { genericWords: ['^stores?$'] }; iD.fileFetcher.cache().preset_presets = { - 'Velero': { tags: { craft: 'sailmaker' }, geometry: ['line'] }, + 'Velero': { tags: { craft: 'sailmaker' }, aliases: ['Velaio'], geometry: ['line'] }, 'Constructor de barco': { tags: { craft: 'boatbuilder' }, geometry: ['line'] }, }; }); @@ -71,57 +71,57 @@ describe('iD.validations.suspicious_name', function () { return issues; } - it('has no errors on init', async () => { + it('has no errors on init', () => { var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('ignores way with no tags', async () => { + it('ignores way with no tags', () => { createWay({}); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('ignores feature with no name', async () => { + it('ignores feature with no name', () => { createWay({ shop: 'supermarket' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('ignores feature with a specific name', async () => { + it('ignores feature with a specific name', () => { createWay({ shop: 'supermarket', name: 'Lou\'s' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('ignores feature with a specific name that includes a generic name', async () => { + it('ignores feature with a specific name that includes a generic name', () => { createWay({ shop: 'supermarket', name: 'Lou\'s Store' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('ignores feature matching excludeNamed pattern in name-suggestion-index', async () => { + it('ignores feature matching excludeNamed pattern in name-suggestion-index', () => { createWay({ shop: 'supermarket', name: 'famiglia cooperativa' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(0); }); - it('flags feature matching a excludeGeneric pattern in name-suggestion-index', async () => { + it('flags feature matching a excludeGeneric pattern in name-suggestion-index', () => { createWay({ shop: 'supermarket', name: 'super mercado' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(1); var issue = issues[0]; @@ -131,10 +131,10 @@ describe('iD.validations.suspicious_name', function () { expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature matching a global exclude pattern in name-suggestion-index', async () => { + it('flags feature matching a global exclude pattern in name-suggestion-index', () => { createWay({ shop: 'supermarket', name: 'store' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(1); var issue = issues[0]; @@ -144,10 +144,10 @@ describe('iD.validations.suspicious_name', function () { expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature with a name that is just a defining tag key', async () => { + it('flags feature with a name that is just a defining tag key', () => { createWay({ amenity: 'drinking_water', name: 'Amenity' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(1); var issue = issues[0]; @@ -157,10 +157,10 @@ describe('iD.validations.suspicious_name', function () { expect(issue.entityIds[0]).to.eql('w-1'); }); - it('flags feature with a name that is just a defining tag value', async () => { + it('flags feature with a name that is just a defining tag value', () => { createWay({ shop: 'red_bicycle_emporium', name: 'Red Bicycle Emporium' }); var validator = iD.validationSuspiciousName(context); - await setTimeout(20); + var issues = validate(validator); expect(issues).to.have.lengthOf(1); var issue = issues[0]; @@ -181,6 +181,17 @@ describe('iD.validations.suspicious_name', function () { expect(issues[0].hash).to.eql('name:ca=Velero'); }); + it('flags feature with a name that matches a preset alias', async () => { + await iD.presetManager.ensureLoaded(true); + createWay({ craft: 'sailmaker', 'name:it': 'Velaio' }); + const validator = iD.validationSuspiciousName(context); + + const issues = validate(validator); + expect(issues).to.have.lengthOf(1); + expect(issues[0].type).to.eql('suspicious_name'); + expect(issues[0].hash).to.eql('name:it=Velaio'); + }); + it('flags feature with a name that matches the preset name and tag name', async () => { await iD.presetManager.ensureLoaded(true); createWay({ craft: 'boatbuilder', 'name:mi': 'boatbuilder', name: 'cOnStRuCtOr de barco' });