diff --git a/CHANGELOG.md b/CHANGELOG.md index 6924a6cd3..ca0c66b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,12 +67,14 @@ _Breaking developer changes, which may affect downstream projects or sites that * Fix walkthrough from not correctly registering deleted ways in "Lines" step ([#10776]) #### :rocket: Presets #### :hammer: Development +* enable Intellisense (IDE auto-completion) for the main classes ([#10618], thanks [@k-yle]) [#7381]: https://github.com/openstreetmap/iD/issues/7381 [#8911]: https://github.com/openstreetmap/iD/pull/8911 [#9634]: https://github.com/openstreetmap/iD/pull/9634 [#9635]: https://github.com/openstreetmap/iD/pull/9635 [#10003]: https://github.com/openstreetmap/iD/pull/10003 +[#10618]: https://github.com/openstreetmap/iD/pull/10618 [#10648]: https://github.com/openstreetmap/iD/pull/10648 [#10720]: https://github.com/openstreetmap/iD/issues/10720 [#10722]: https://github.com/openstreetmap/iD/pull/10722 diff --git a/modules/core/context.js b/modules/core/context.js index c49245af7..f2bd0b48e 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -25,7 +25,7 @@ import { utilKeybinding, utilRebind, utilStringQs, utilCleanOsmString } from '.. export function coreContext() { const dispatch = d3_dispatch('enter', 'exit', 'change'); - let context = utilRebind({}, dispatch, 'on'); + const context = {}; let _deferred = new Set(); context.version = packageJSON.version; @@ -591,5 +591,5 @@ export function coreContext() { } }; - return context; + return utilRebind(context, dispatch, 'on'); } diff --git a/modules/core/uploader.js b/modules/core/uploader.js index 605a26c43..2a438e6d8 100644 --- a/modules/core/uploader.js +++ b/modules/core/uploader.js @@ -39,7 +39,7 @@ export function coreUploader(context) { .then(function(d) { _discardTags = d; }) .catch(function() { /* ignore */ }); - var uploader = utilRebind({}, dispatch, 'on'); + const uploader = {}; uploader.isSaving = function() { return _isSaving; @@ -402,5 +402,5 @@ export function coreUploader(context) { }; - return uploader; + return utilRebind(uploader, dispatch, 'on'); } diff --git a/modules/core/validator.js b/modules/core/validator.js index 24cb4cf53..ce5cf4b22 100644 --- a/modules/core/validator.js +++ b/modules/core/validator.js @@ -10,7 +10,7 @@ import * as Validations from '../validations/index'; export function coreValidator(context) { let dispatch = d3_dispatch('validated', 'focusedIssue'); - let validator = utilRebind({}, dispatch, 'on'); + const validator = {}; let _rules = {}; let _disabledRules = {}; @@ -784,7 +784,7 @@ export function coreValidator(context) { } - return validator; + return utilRebind(validator, dispatch, 'on'); } diff --git a/modules/globals.d.ts b/modules/globals.d.ts index aa64619bc..cc1d6e642 100644 --- a/modules/globals.d.ts +++ b/modules/globals.d.ts @@ -7,6 +7,30 @@ declare global { declare var before: typeof beforeEach; declare var after: typeof afterEach; declare var VITEST: true; + + declare type Tags = { [key: string]: string }; + + declare namespace iD { + export type Context = ReturnType; + + export type Graph = InstanceType; + + export type OsmNode = import('./osm/node').OsmNode; + export type OsmWay = import('./osm/way').OsmWay; + export type OsmRelation = import('./osm/relation').OsmRelation; + + export type AbstractEntity = InstanceType; + export type OsmEntity = OsmNode | OsmWay | OsmRelation; + } + + declare namespace d3 { + export type Selection = import('d3').Selection< + T, + unknown, + unknown, + unknown + >; + } } export {}; diff --git a/modules/osm/entity.js b/modules/osm/entity.js index 155fe6438..e33b0a552 100644 --- a/modules/osm/entity.js +++ b/modules/osm/entity.js @@ -80,6 +80,7 @@ osmEntity.deprecatedTagValuesByKey = function(dataDeprecated) { osmEntity.prototype = { + /** @type {Tags} */ tags: {}, diff --git a/modules/osm/node.js b/modules/osm/node.js index a6f07e808..5cfe01554 100644 --- a/modules/osm/node.js +++ b/modules/osm/node.js @@ -21,6 +21,10 @@ export const cardinal = { northnorthwest: 337, nnw: 337 }; +/** + * @typedef {typeof prototype & iD.AbstractEntity} OsmNode + * @returns {OsmNode} + */ export function osmNode() { if (!(this instanceof osmNode)) { return (new osmNode()).initialize(arguments); @@ -33,7 +37,7 @@ osmEntity.node = osmNode; osmNode.prototype = Object.create(osmEntity.prototype); -Object.assign(osmNode.prototype, { +const prototype = { type: 'node', loc: [9999, 9999], @@ -243,4 +247,5 @@ Object.assign(osmNode.prototype, { coordinates: this.loc }; } -}); +}; +Object.assign(osmNode.prototype, prototype); diff --git a/modules/osm/relation.js b/modules/osm/relation.js index 8d2511504..66d50269b 100644 --- a/modules/osm/relation.js +++ b/modules/osm/relation.js @@ -4,7 +4,10 @@ import { osmEntity } from './entity'; import { osmJoinWays } from './multipolygon'; import { geoExtent, geoPolygonContainsPolygon, geoPolygonIntersectsPolygon } from '../geo'; - +/** + * @typedef {typeof prototype & iD.AbstractEntity} OsmRelation + * @returns {OsmRelation} + */ export function osmRelation() { if (!(this instanceof osmRelation)) { return (new osmRelation()).initialize(arguments); @@ -28,7 +31,7 @@ osmRelation.creationOrder = function(a, b) { }; -Object.assign(osmRelation.prototype, { +const prototype = { type: 'relation', members: [], @@ -363,4 +366,5 @@ Object.assign(osmRelation.prototype, { return result; } -}); +}; +Object.assign(osmRelation.prototype, prototype); diff --git a/modules/osm/way.js b/modules/osm/way.js index 8c312d0a0..3555c7641 100644 --- a/modules/osm/way.js +++ b/modules/osm/way.js @@ -6,7 +6,10 @@ import { osmLanes } from './lanes'; import { osmTagSuggestingArea, osmRightSideIsInsideTags, osmRemoveLifecyclePrefix, osmOneWayBiDirectionalTags, osmOneWayBackwardTags, osmOneWayForwardTags, osmOneWayTags } from './tags'; import { utilArrayUniq, utilCheckTagDictionary } from '../util'; - +/** + * @typedef {typeof prototype & iD.AbstractEntity} OsmWay + * @returns {OsmWay} + */ export function osmWay() { if (!(this instanceof osmWay)) { return (new osmWay()).initialize(arguments); @@ -21,7 +24,7 @@ osmEntity.way = osmWay; osmWay.prototype = Object.create(osmEntity.prototype); -Object.assign(osmWay.prototype, { +const prototype = { type: 'way', nodes: [], @@ -536,7 +539,8 @@ Object.assign(osmWay.prototype, { return isNaN(area) ? 0 : area; }); } -}); +}; +Object.assign(osmWay.prototype, prototype); // Filter function to eliminate consecutive duplicates. diff --git a/modules/renderer/features.js b/modules/renderer/features.js index 78c2c1ecd..36e702260 100644 --- a/modules/renderer/features.js +++ b/modules/renderer/features.js @@ -8,7 +8,7 @@ import { utilArrayGroupBy, utilArrayUnion, utilQsString, utilStringQs } from '.. export function rendererFeatures(context) { var dispatch = d3_dispatch('change', 'redraw'); - var features = utilRebind({}, dispatch, 'on'); + const features = {}; var _deferred = new Set(); var traffic_roads = { @@ -616,5 +616,5 @@ export function rendererFeatures(context) { }); - return features; + return utilRebind(features, dispatch, 'on'); } diff --git a/modules/util/rebind.js b/modules/util/rebind.js index eead6dd0a..e358a42b4 100644 --- a/modules/util/rebind.js +++ b/modules/util/rebind.js @@ -1,8 +1,16 @@ // Copies a variable number of methods from source to target. -export function utilRebind(target, source) { - var i = 1, n = arguments.length, method; - while (++i < n) { - target[method = arguments[i]] = d3_rebind(target, source, source[method]); +/** + * @template T + * @template S + * @template {keyof S} Args + * @param {T} target + * @param {S} source + * @param {...Args} args + * @returns {T & Pick} + */ +export function utilRebind(target, source, ...args) { + for (const method of args) { + target[method] = d3_rebind(target, source, source[method]); } return target; } diff --git a/test/spec/util/rebind.js b/test/spec/util/rebind.js new file mode 100644 index 000000000..ad5af5a44 --- /dev/null +++ b/test/spec/util/rebind.js @@ -0,0 +1,19 @@ +describe('utilRebind', () => { + it('copies methods from source to target', () => { + const target = { original: 123 }; + const source = new class { + value = 456; + method() { + return this.value; + } + }; + + const copied = iD.utilRebind(target, source, 'method'); + + expect(copied).toStrictEqual({ + original: 123, + method: expect.any(Function) + }); + expect(copied.method()).toBe(456); + }); +});