diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js index d462c3555..ef5c283f2 100644 --- a/modules/validations/crossing_ways.js +++ b/modules/validations/crossing_ways.js @@ -16,7 +16,7 @@ import { actionAddMidpoint } from '../actions'; import { geoChooseEdge } from '../geo'; -export function validationHighwayCrossingOtherWays(context) { +export function validationHighwayCrossingOtherWays() { // Check if the edge going from n1 to n2 crosses (without a connection node) // any edge on way. Return the corss point if so. function findEdgeToWayCrossCoords(n1, n2, way, graph, edgePairsVisited) { @@ -236,11 +236,14 @@ export function validationHighwayCrossingOtherWays(context) { return edgeCrossInfos; } - var validation = function(entitiesToCheck, graph, tree) { + var validation = function(entity, context) { + + var graph = context.graph(); + var tree = context.history().tree(); // create one issue per crossing point var edgePairsVisited = d3_set(), issues = []; - var waysToCheck = _flattenDeep(_map(entitiesToCheck, function(entity) { + var waysToCheck = _flattenDeep(_map([entity], function(entity) { if (!getFeatureTypeForTags(entity.tags)) { return []; } diff --git a/modules/validations/deprecated_tag.js b/modules/validations/deprecated_tag.js index 9305bc864..651ed72c5 100644 --- a/modules/validations/deprecated_tag.js +++ b/modules/validations/deprecated_tag.js @@ -15,70 +15,67 @@ import { } from '../actions'; -export function validationDeprecatedTag(context) { +export function validationDeprecatedTag() { - var validation = function(entitiesToCheck) { + var validation = function(change, context) { var issues = []; - for (var changeIndex in entitiesToCheck) { - var change = entitiesToCheck[changeIndex]; - var deprecatedTagsArray = change.deprecatedTags(); - if (deprecatedTagsArray.length > 0) { - for (var deprecatedTagIndex in deprecatedTagsArray) { - var deprecatedTags = deprecatedTagsArray[deprecatedTagIndex]; - var tagsLabel = utilTagText({ tags: deprecatedTags.old }); - var featureLabel = utilDisplayLabel(change, context); - issues.push(new validationIssue({ - type: ValidationIssueType.deprecated_tags, - severity: ValidationIssueSeverity.warning, - message: t('issues.deprecated_tags.message', { feature: featureLabel, tags: tagsLabel }), - tooltip: t('issues.deprecated_tags.tip'), - entities: [change], - hash: tagsLabel, - info: { - oldTags: deprecatedTags.old, - replaceTags: deprecatedTags.replace - }, - fixes: [ - new validationIssueFix({ - title: t('issues.fix.upgrade_tags.title'), - onClick: function() { - var entity = this.issue.entities[0]; - var tags = _clone(entity.tags); - var replaceTags = this.issue.info.replaceTags; - var oldTags = this.issue.info.oldTags; - var transferValue; - for (var oldTagKey in oldTags) { - if (oldTags[oldTagKey] === '*') { - transferValue = tags[oldTagKey]; - } - delete tags[oldTagKey]; + var deprecatedTagsArray = change.deprecatedTags(); + if (deprecatedTagsArray.length > 0) { + for (var deprecatedTagIndex in deprecatedTagsArray) { + var deprecatedTags = deprecatedTagsArray[deprecatedTagIndex]; + var tagsLabel = utilTagText({ tags: deprecatedTags.old }); + var featureLabel = utilDisplayLabel(change, context); + issues.push(new validationIssue({ + type: ValidationIssueType.deprecated_tags, + severity: ValidationIssueSeverity.warning, + message: t('issues.deprecated_tags.message', { feature: featureLabel, tags: tagsLabel }), + tooltip: t('issues.deprecated_tags.tip'), + entities: [change], + hash: tagsLabel, + info: { + oldTags: deprecatedTags.old, + replaceTags: deprecatedTags.replace + }, + fixes: [ + new validationIssueFix({ + title: t('issues.fix.upgrade_tags.title'), + onClick: function() { + var entity = this.issue.entities[0]; + var tags = _clone(entity.tags); + var replaceTags = this.issue.info.replaceTags; + var oldTags = this.issue.info.oldTags; + var transferValue; + for (var oldTagKey in oldTags) { + if (oldTags[oldTagKey] === '*') { + transferValue = tags[oldTagKey]; } - for (var replaceKey in replaceTags) { - var replaceValue = replaceTags[replaceKey]; - if (replaceValue === '*') { - if (tags[replaceKey]) { - // any value is okay and there already - // is one, so don't update it - continue; - } else { - // otherwise assume `yes` is okay - tags[replaceKey] = 'yes'; - } - } else if (replaceValue === '$1') { - tags[replaceKey] = transferValue; - } else { - tags[replaceKey] = replaceValue; - } - } - context.perform( - actionChangeTags(entity.id, tags), - t('issues.fix.upgrade_tags.undo_redo') - ); + delete tags[oldTagKey]; } - }) - ] - })); - } + for (var replaceKey in replaceTags) { + var replaceValue = replaceTags[replaceKey]; + if (replaceValue === '*') { + if (tags[replaceKey]) { + // any value is okay and there already + // is one, so don't update it + continue; + } else { + // otherwise assume `yes` is okay + tags[replaceKey] = 'yes'; + } + } else if (replaceValue === '$1') { + tags[replaceKey] = transferValue; + } else { + tags[replaceKey] = replaceValue; + } + } + context.perform( + actionChangeTags(entity.id, tags), + t('issues.fix.upgrade_tags.undo_redo') + ); + } + }) + ] + })); } } diff --git a/modules/validations/disconnected_highway.js b/modules/validations/disconnected_highway.js index 4fa085b96..17722cb71 100644 --- a/modules/validations/disconnected_highway.js +++ b/modules/validations/disconnected_highway.js @@ -11,7 +11,7 @@ import { import { operationDelete } from '../operations/index'; import { modeDrawLine } from '../modes'; -export function validationDisconnectedHighway(context) { +export function validationDisconnectedHighway() { function isDisconnectedHighway(entity, graph) { if (!entity.tags.highway) return false; @@ -32,52 +32,50 @@ export function validationDisconnectedHighway(context) { } - var validation = function(entitiesToCheck, graph) { + var validation = function(entity, context) { var issues = []; - for (var i = 0; i < entitiesToCheck.length; i++) { - var entity = entitiesToCheck[i]; - if (isDisconnectedHighway(entity, graph)) { - var entityLabel = utilDisplayLabel(entity, context); + var graph = context.graph(); + if (isDisconnectedHighway(entity, graph)) { + var entityLabel = utilDisplayLabel(entity, context); - issues.push(new validationIssue({ - type: ValidationIssueType.disconnected_highway, - severity: ValidationIssueSeverity.warning, - message: t('issues.disconnected_highway.message', {highway: entityLabel}), - tooltip: t('issues.disconnected_highway.tip'), - entities: [entity], - fixes: [ - new validationIssueFix({ - title: t('issues.fix.continue_feature.title'), - onClick: function() { - var way = this.issue.entities[0]; - var childNodes = context.graph().childNodes(way); - var endNodes = [childNodes[0], childNodes[childNodes.length-1]]; - var exclusiveEndNodes = endNodes.filter(function(vertex) { - return graph.parentWays(vertex).length === 1; - }); - var vertex; - if (exclusiveEndNodes.length === 1) { - // prefer an endpoint with no connecting ways - vertex = exclusiveEndNodes[0]; - } else { - // prefer the terminating node - vertex = endNodes[1]; - } - context.enter( - modeDrawLine(context, way.id, context.graph(), way.affix(vertex.id), true) - ); + issues.push(new validationIssue({ + type: ValidationIssueType.disconnected_highway, + severity: ValidationIssueSeverity.warning, + message: t('issues.disconnected_highway.message', {highway: entityLabel}), + tooltip: t('issues.disconnected_highway.tip'), + entities: [entity], + fixes: [ + new validationIssueFix({ + title: t('issues.fix.continue_feature.title'), + onClick: function() { + var way = this.issue.entities[0]; + var childNodes = context.graph().childNodes(way); + var endNodes = [childNodes[0], childNodes[childNodes.length-1]]; + var exclusiveEndNodes = endNodes.filter(function(vertex) { + return graph.parentWays(vertex).length === 1; + }); + var vertex; + if (exclusiveEndNodes.length === 1) { + // prefer an endpoint with no connecting ways + vertex = exclusiveEndNodes[0]; + } else { + // prefer the terminating node + vertex = endNodes[1]; } - }), - new validationIssueFix({ - title: t('issues.fix.delete_feature.title'), - onClick: function() { - var id = this.issue.entities[0].id; - operationDelete([id], context)(); - } - }) - ] - })); - } + context.enter( + modeDrawLine(context, way.id, context.graph(), way.affix(vertex.id), true) + ); + } + }), + new validationIssueFix({ + title: t('issues.fix.delete_feature.title'), + onClick: function() { + var id = this.issue.entities[0].id; + operationDelete([id], context)(); + } + }) + ] + })); } return issues; diff --git a/modules/validations/generic_name.js b/modules/validations/generic_name.js index 21ac4d5da..604eb4959 100644 --- a/modules/validations/generic_name.js +++ b/modules/validations/generic_name.js @@ -44,36 +44,32 @@ export function validationGenericName(context) { } - return function validation(entitiesToCheck) { + return function validation(entity) { var issues = []; - - for (var i = 0; i < entitiesToCheck.length; i++) { - var change = entitiesToCheck[i]; - var generic = isGenericName(change); - if (generic) { - var preset = utilPreset(change, context); - issues.push(new validationIssue({ - type: ValidationIssueType.generic_name, - severity: ValidationIssueSeverity.warning, - message: t('issues.generic_name.message', {feature: preset.name(), name: generic}), - tooltip: t('issues.generic_name.tip'), - entities: [change], - fixes: [ - new validationIssueFix({ - title: t('issues.fix.remove_generic_name.title'), - onClick: function() { - var entity = this.issue.entities[0]; - var tags = _clone(entity.tags); - delete tags.name; - context.perform( - actionChangeTags(entity.id, tags), - t('issues.fix.remove_generic_name.undo_redo') - ); - } - }) - ] - })); - } + var generic = isGenericName(entity); + if (generic) { + var preset = utilPreset(entity, context); + issues.push(new validationIssue({ + type: ValidationIssueType.generic_name, + severity: ValidationIssueSeverity.warning, + message: t('issues.generic_name.message', {feature: preset.name(), name: generic}), + tooltip: t('issues.generic_name.tip'), + entities: [entity], + fixes: [ + new validationIssueFix({ + title: t('issues.fix.remove_generic_name.title'), + onClick: function() { + var entity = this.issue.entities[0]; + var tags = _clone(entity.tags); + delete tags.name; + context.perform( + actionChangeTags(entity.id, tags), + t('issues.fix.remove_generic_name.undo_redo') + ); + } + }) + ] + })); } return issues; diff --git a/modules/validations/highway_almost_junction.js b/modules/validations/highway_almost_junction.js index c17717988..62b708f11 100644 --- a/modules/validations/highway_almost_junction.js +++ b/modules/validations/highway_almost_junction.js @@ -25,7 +25,7 @@ import { /** * Look for roads that can be connected to other roads with a short extension */ -export function validationHighwayAlmostJunction(context) { +export function validationHighwayAlmostJunction() { function isHighway(entity) { return entity.type === 'way' && entity.tags.highway && entity.tags.highway !== 'no'; @@ -108,60 +108,59 @@ export function validationHighwayAlmostJunction(context) { return null; } - var validation = function(entitiesToCheck, graph, tree) { + var validation = function(endHighway, context) { + var graph = context.graph(); + var tree = context.history().tree(); var issues = []; - for (var i = 0; i < entitiesToCheck.length; i++) { - var endHighway = entitiesToCheck[i]; - if (!isHighway(endHighway)) continue; - var extendableNodes = findConnectableEndNodesByExtension(endHighway, graph, tree); - for (var j = 0; j < extendableNodes.length; j++) { - var node = extendableNodes[j].node; - var edgeHighway = graph.entity(extendableNodes[j].wid); + if (!isHighway(endHighway)) return issues; + var extendableNodes = findConnectableEndNodesByExtension(endHighway, graph, tree); + for (var j = 0; j < extendableNodes.length; j++) { + var node = extendableNodes[j].node; + var edgeHighway = graph.entity(extendableNodes[j].wid); - var fixes = [ - new validationIssueFix({ - title: t('issues.fix.connect_almost_junction.title'), - onClick: function() { - var endNode = this.issue.entities[1], - targetEdge = this.issue.info.edge, - crossLoc = this.issue.info.cross_loc; - context.perform( - actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode), - t('issues.fix.connect_almost_junction.undo_redo') - ); - } - }) - ]; - if (Object.keys(node.tags).length === 0) { - // node has no tags, suggest noexit fix - fixes.push(new validationIssueFix({ - title: t('issues.fix.tag_as_disconnected.title'), - onClick: function() { - var nodeID = this.issue.entities[1].id; - context.perform( - actionChangeTags(nodeID, {noexit: 'yes'}), - t('issues.fix.tag_as_disconnected.undo_redo') - ); - } - })); - } - issues.push(new validationIssue({ - type: ValidationIssueType.highway_almost_junction, - severity: ValidationIssueSeverity.warning, - message: t('issues.highway_almost_junction.message', { - highway: utilDisplayLabel(endHighway, context), - highway2: utilDisplayLabel(edgeHighway, context) - }), - tooltip: t('issues.highway_almost_junction.tip'), - entities: [endHighway, node, edgeHighway], - coordinates: extendableNodes[j].node.loc, - info: { - edge: extendableNodes[j].edge, - cross_loc: extendableNodes[j].cross_loc - }, - fixes: fixes + var fixes = [ + new validationIssueFix({ + title: t('issues.fix.connect_almost_junction.title'), + onClick: function() { + var endNode = this.issue.entities[1], + targetEdge = this.issue.info.edge, + crossLoc = this.issue.info.cross_loc; + context.perform( + actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode), + t('issues.fix.connect_almost_junction.undo_redo') + ); + } + }) + ]; + if (Object.keys(node.tags).length === 0) { + // node has no tags, suggest noexit fix + fixes.push(new validationIssueFix({ + title: t('issues.fix.tag_as_disconnected.title'), + onClick: function() { + var nodeID = this.issue.entities[1].id; + context.perform( + actionChangeTags(nodeID, {noexit: 'yes'}), + t('issues.fix.tag_as_disconnected.undo_redo') + ); + } })); } + issues.push(new validationIssue({ + type: ValidationIssueType.highway_almost_junction, + severity: ValidationIssueSeverity.warning, + message: t('issues.highway_almost_junction.message', { + highway: utilDisplayLabel(endHighway, context), + highway2: utilDisplayLabel(edgeHighway, context) + }), + tooltip: t('issues.highway_almost_junction.tip'), + entities: [endHighway, node, edgeHighway], + coordinates: extendableNodes[j].node.loc, + info: { + edge: extendableNodes[j].edge, + cross_loc: extendableNodes[j].cross_loc + }, + fixes: fixes + })); } return issues; diff --git a/modules/validations/issue_manager.js b/modules/validations/issue_manager.js index 3924ea125..84c9ef379 100644 --- a/modules/validations/issue_manager.js +++ b/modules/validations/issue_manager.js @@ -55,23 +55,22 @@ export function IssueManager(context) { }; var entityValidations = [ - validations.validationDeprecatedTag, - validations.validationDisconnectedHighway, - validations.validationGenericName, - validations.validationHighwayCrossingOtherWays, - validations.validationHighwayAlmostJunction, - validations.validationMapCSSChecks, - validations.validationMissingTag, - validations.validationOldMultipolygon, - validations.validationTagSuggestsArea + validations.validationDeprecatedTag(), + validations.validationDisconnectedHighway(), + validations.validationGenericName(), + validations.validationHighwayCrossingOtherWays(), + validations.validationHighwayAlmostJunction(), + validations.validationMapCSSChecks(), + validations.validationMissingTag(), + validations.validationOldMultipolygon(), + validations.validationTagSuggestsArea() ]; function validateEntity(entity) { - var history = context.history(); return _flatten(_map( entityValidations, function(fn) { - return fn(context)([entity], history.graph(), history.tree()); + return fn(entity, context); } )); } @@ -86,7 +85,7 @@ export function IssueManager(context) { var entitiesToCheck = changes.created.concat(changes.modified); var graph = history.graph(); - issues = issues.concat(validations.validationManyDeletions(context)(changes, graph)); + issues = issues.concat(validations.validationManyDeletions()(changes, context)); entitiesToCheck = _uniq(_flattenDeep(_map(entitiesToCheck, function(entity) { var entities = [entity]; diff --git a/modules/validations/many_deletions.js b/modules/validations/many_deletions.js index 01420ba4c..9978dacb9 100644 --- a/modules/validations/many_deletions.js +++ b/modules/validations/many_deletions.js @@ -8,10 +8,10 @@ import { export function validationManyDeletions() { var threshold = 100; - var validation = function(changes, graph) { + var validation = function(changes, context) { var issues = []; var nodes = 0, ways = 0, areas = 0, relations = 0; - + var graph = context.graph(); changes.deleted.forEach(function(c) { if (c.type === 'node') { nodes++; } else if (c.type === 'way' && c.geometry(graph) === 'line') { ways++; } diff --git a/modules/validations/mapcss_checks.js b/modules/validations/mapcss_checks.js index 5bae6425e..adf4fcaa9 100644 --- a/modules/validations/mapcss_checks.js +++ b/modules/validations/mapcss_checks.js @@ -1,17 +1,17 @@ import { services } from '../services'; export function validationMapCSSChecks() { - var validation = function(entitiesToCheck, graph) { + var validation = function(entity, context) { if (!services.maprules) return []; + var graph = context.graph(); + var rules = services.maprules.validationRules(); var issues = []; for (var i = 0; i < rules.length; i++) { var rule = rules[i]; - for (var j = 0; j < entitiesToCheck.length; j++) { - rule.findIssues(entitiesToCheck[j], graph, issues); - } + rule.findIssues(entity, graph, issues); } return issues; diff --git a/modules/validations/missing_tag.js b/modules/validations/missing_tag.js index 4c2006988..02364322c 100644 --- a/modules/validations/missing_tag.js +++ b/modules/validations/missing_tag.js @@ -12,7 +12,7 @@ import { } from './validation_issue'; import { operationDelete } from '../operations/index'; -export function validationMissingTag(context) { +export function validationMissingTag() { function hasDescriptiveTags(entity) { var keys = _without(Object.keys(entity.tags), 'area', 'name').filter(osmIsInterestingTag); @@ -22,40 +22,37 @@ export function validationMissingTag(context) { return keys.length > 0; } - var validation = function(entitiesToCheck, graph) { + var validation = function(entity, context) { var types = ['point', 'line', 'area', 'relation']; var issues = []; - - for (var i = 0; i < entitiesToCheck.length; i++) { - var entity = entitiesToCheck[i]; - var geometry = entity.geometry(graph); - // ignore vertex features - if (types.indexOf(geometry) !== -1 && - !(hasDescriptiveTags(entity) || entity.hasParentRelations(graph))) { - var entityLabel = utilDisplayLabel(entity, context); - issues.push(new validationIssue({ - type: ValidationIssueType.missing_tag, - severity: ValidationIssueSeverity.error, - message: t('issues.untagged_feature.message', {feature: entityLabel}), - tooltip: t('issues.untagged_feature.tip'), - entities: [entity], - fixes: [ - new validationIssueFix({ - title: t('issues.fix.select_preset.title'), - onClick: function() { - context.ui().sidebar.showPresetList(); - } - }), - new validationIssueFix({ - title: t('issues.fix.delete_feature.title'), - onClick: function() { - var id = this.issue.entities[0].id; - operationDelete([id], context)(); - } - }) - ] - })); - } + var graph = context.graph(); + var geometry = entity.geometry(graph); + // ignore vertex features + if (types.indexOf(geometry) !== -1 && + !(hasDescriptiveTags(entity) || entity.hasParentRelations(graph))) { + var entityLabel = utilDisplayLabel(entity, context); + issues.push(new validationIssue({ + type: ValidationIssueType.missing_tag, + severity: ValidationIssueSeverity.error, + message: t('issues.untagged_feature.message', {feature: entityLabel}), + tooltip: t('issues.untagged_feature.tip'), + entities: [entity], + fixes: [ + new validationIssueFix({ + title: t('issues.fix.select_preset.title'), + onClick: function() { + context.ui().sidebar.showPresetList(); + } + }), + new validationIssueFix({ + title: t('issues.fix.delete_feature.title'), + onClick: function() { + var id = this.issue.entities[0].id; + operationDelete([id], context)(); + } + }) + ] + })); } return issues; diff --git a/modules/validations/old_multipolygon.js b/modules/validations/old_multipolygon.js index 69870bc0f..70a358074 100644 --- a/modules/validations/old_multipolygon.js +++ b/modules/validations/old_multipolygon.js @@ -11,41 +11,39 @@ import { actionChangeTags } from '../actions'; -export function validationOldMultipolygon(context) { +export function validationOldMultipolygon() { - return function validation(entitiesToCheck, graph) { + return function validation(entity, context) { var issues = []; - for (var i = 0; i < entitiesToCheck.length; i++) { - var entity = entitiesToCheck[i]; - var mistaggedMultipolygon = osmIsSimpleMultipolygonOuterMember(entity, graph); - if (mistaggedMultipolygon) { - var multipolygonLabel = utilDisplayLabel(mistaggedMultipolygon, context); - issues.push(new validationIssue({ - type: ValidationIssueType.old_multipolygon, - severity: ValidationIssueSeverity.warning, - message: t('issues.old_multipolygon.message', {multipolygon: multipolygonLabel}), - tooltip: t('issues.old_multipolygon.tip'), - entities: [entity, mistaggedMultipolygon], - fixes: [ - new validationIssueFix({ - title: t('issues.fix.move_tags.title'), - onClick: function() { - var outerWay = this.issue.entities[0]; - var multipolygon = this.issue.entities[1]; - context.perform( - function(graph) { - multipolygon = multipolygon.mergeTags(outerWay.tags); - graph = graph.replace(multipolygon); - graph = actionChangeTags(outerWay.id, {})(graph); - return graph; - }, - t('issues.fix.move_tags.undo_redo') - ); - } - }) - ] - })); - } + var graph = context.graph(); + var mistaggedMultipolygon = osmIsSimpleMultipolygonOuterMember(entity, graph); + if (mistaggedMultipolygon) { + var multipolygonLabel = utilDisplayLabel(mistaggedMultipolygon, context); + issues.push(new validationIssue({ + type: ValidationIssueType.old_multipolygon, + severity: ValidationIssueSeverity.warning, + message: t('issues.old_multipolygon.message', {multipolygon: multipolygonLabel}), + tooltip: t('issues.old_multipolygon.tip'), + entities: [entity, mistaggedMultipolygon], + fixes: [ + new validationIssueFix({ + title: t('issues.fix.move_tags.title'), + onClick: function() { + var outerWay = this.issue.entities[0]; + var multipolygon = this.issue.entities[1]; + context.perform( + function(graph) { + multipolygon = multipolygon.mergeTags(outerWay.tags); + graph = graph.replace(multipolygon); + graph = actionChangeTags(outerWay.id, {})(graph); + return graph; + }, + t('issues.fix.move_tags.undo_redo') + ); + } + }) + ] + })); } return issues; }; diff --git a/modules/validations/tag_suggests_area.js b/modules/validations/tag_suggests_area.js index 5b1f1f1c4..f6f1634d9 100644 --- a/modules/validations/tag_suggests_area.js +++ b/modules/validations/tag_suggests_area.js @@ -17,7 +17,7 @@ import { // https://github.com/openstreetmap/josm/blob/mirror/src/org/ // openstreetmap/josm/data/validation/tests/UnclosedWays.java#L80 -export function validationTagSuggestsArea(context) { +export function validationTagSuggestsArea() { function tagSuggestsArea(tags) { if (_isEmpty(tags)) return false; @@ -40,39 +40,37 @@ export function validationTagSuggestsArea(context) { } - var validation = function(entitiesToCheck, graph) { + var validation = function(entity, context) { var issues = []; - for (var i = 0; i < entitiesToCheck.length; i++) { - var entity = entitiesToCheck[i]; - var geometry = entity.geometry(graph); - var suggestingTags = (geometry === 'line' ? tagSuggestsArea(entity.tags) : undefined); + var graph = context.graph(); + var geometry = entity.geometry(graph); + var suggestingTags = (geometry === 'line' ? tagSuggestsArea(entity.tags) : undefined); - if (suggestingTags) { - var tagText = utilTagText({ tags: suggestingTags }); - issues.push(new validationIssue({ - type: ValidationIssueType.tag_suggests_area, - severity: ValidationIssueSeverity.warning, - message: t('issues.tag_suggests_area.message', { tag: tagText }), - tooltip: t('issues.tag_suggests_area.tip'), - entities: [entity], - fixes: [ - new validationIssueFix({ - title: t('issues.fix.remove_tags.title'), - onClick: function() { - var entity = this.issue.entities[0]; - var tags = _clone(entity.tags); - for (var key in suggestingTags) { - delete tags[key]; - } - context.perform( - actionChangeTags(entity.id, tags), - t('issues.fix.remove_tags.undo_redo') - ); + if (suggestingTags) { + var tagText = utilTagText({ tags: suggestingTags }); + issues.push(new validationIssue({ + type: ValidationIssueType.tag_suggests_area, + severity: ValidationIssueSeverity.warning, + message: t('issues.tag_suggests_area.message', { tag: tagText }), + tooltip: t('issues.tag_suggests_area.tip'), + entities: [entity], + fixes: [ + new validationIssueFix({ + title: t('issues.fix.remove_tags.title'), + onClick: function() { + var entity = this.issue.entities[0]; + var tags = _clone(entity.tags); + for (var key in suggestingTags) { + delete tags[key]; } - }) - ] - })); - } + context.perform( + actionChangeTags(entity.id, tags), + t('issues.fix.remove_tags.undo_redo') + ); + } + }) + ] + })); } return issues;