Final vsCode with JSDOC - WazeDev/WazeDev.github.io GitHub Wiki

Adding JSDoc Comments for Smarter Code Editing

Modern code editors like VS Code, WebStorm, and many browser-based IDEs can leverage special code comments called JSDoc to give you powerful features such as:

  • Type hints & autocomplete
  • Inline documentation tooltips
  • Better code navigation and refactoring

What is JSDoc?

JSDoc is a standard for documenting JavaScript code. You add special comments above your functions, classes, and variables, and your code editor will use them to:

  • Show descriptions for functions or parameters
  • Show expected types (objects, arrays, numbers, etc.)
  • Warn you about bugs before you run your code
  • Improve your code understanding

JSDoc Examples from Our Script

Here are some examples showing different JSDoc patterns used in our WazeDev script:

Example: Annotating a Function

/**
 * Draws highlight overlay for currently selected segments
 * Only highlights segments when the script is enabled
 * @returns {void}
 */
function drawSelectionHighlight() {
    // ... function code as before ...
}

Documenting Function Parameters

/**
 * Sets the checked state of a checkbox element
 * @param {string} checkboxId - The ID of the checkbox element
 * @param {boolean} checked - Whether the checkbox should be checked
 * @returns {void}
 */
function setChecked(checkboxId, checked) {
    const checkbox = document.getElementById(checkboxId);
    if (checkbox) checkbox.checked = checked;
}

Documenting Complex Objects

/**
 * Global settings object containing user preferences
 * @type {Object}
 * @property {boolean} Enabled - Whether the script highlighting is active
 */
let settings = {};

Documenting Async Functions

/**
 * Main initialization function that sets up the complete user interface
 * Loads settings, populates user information, fetches profile data, and initializes map layer
 * @async
 * @returns {Promise<void>} Promise that resolves when initialization is complete
 */
async function main() {
    // ... function code
}

Benefits You'll See in Your Editor

With JSDoc comments added, modern editors provide:

IntelliSense/Autocomplete:

  • Function names, parameters, and return types appear as you type
  • Descriptions appear in popup tooltips
  • Reduces typos and API lookup time

Parameter Validation:

  • Warnings when you pass wrong types (string instead of number, etc.)
  • Highlighted missing required parameters
  • Better error detection before running code

Code Navigation:

  • Click-to-definition works better
  • Find all references to functions
  • Easier refactoring with confidence

Team Collaboration:

  • New team members understand function purposes immediately
  • Self-documenting code reduces need for external documentation
  • Consistent parameter naming and typing across the project

Common JSDoc Tags Reference

Tag Purpose Example
@param {type} name - description Document parameters @param {string} userId - The user's ID
@returns {type} description Document return value @returns {boolean} True if successful
@throws {type} description Document exceptions @throws {Error} When network fails
@async Mark async functions @async
@example Show usage examples @example setChecked('myId', true)
@since Version when added @since 1.2.0
@deprecated Mark as deprecated @deprecated Use newFunction() instead

Pro tip: Most editors will auto-generate JSDoc templates when you type /** above a function and press Enter!


Setting Up Your Editor

Visual Studio Code:

  • JSDoc support is built-in - just start typing /** above functions
  • Install "JavaScript (ES6) code snippets" extension for more templates
  • Enable "typescript.suggest.autoImports" in settings for better IntelliSense

WebStorm/IntelliJ:

  • JSDoc support is built-in and very comprehensive
  • Use Ctrl+Shift+D (Windows) or Cmd+Shift+D (Mac) to generate JSDoc templates
  • Configure JSDoc validation in Settings → Languages & Frameworks → JavaScript

Browser-based Editors (CodePen, JSFiddle, etc.):

  • Most modern browser editors support basic JSDoc IntelliSense
  • May have limited features compared to desktop editors

Below is the complete script with JSDOC support

// ==UserScript==
// @name         WazeDev First Script              // Display name in Tampermonkey dashboard
// @namespace    http://tampermonkey.net/          // Unique identifier (use your GreasyFork profile for updates)
// @version      0.1                               // Version number for update tracking
// @description  Learning to script!               // Brief description of functionality
// @author       You                               // Your name or username
// @include      https://beta.waze.com/*           // Run on beta WME (testing environment)
// @include      https://www.waze.com/editor*      // Run on production WME
// @include      https://www.waze.com/*/editor*    // Run on localized WME URLs
// @exclude      https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude      https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant        none                              // No special permissions needed
// ==/UserScript==

/**
 * @typedef {Object} UserSession
 * @property {string} userName - The user's display name
 * @property {number} rank - The user's editing rank
 * @property {boolean} isAreaManager - Whether user is an area manager
 * @property {boolean} isCountryManager - Whether user is a country manager
 */

/**
 * @typedef {Object} EditCountByType
 * @property {number} segments - Number of segment edits
 * @property {number} venues - Number of venue edits
 * @property {number} mapProblems - Number of map problem edits
 * @property {number} updateRequests - Number of update request edits
 * @property {number} placeUpdateRequests - Number of place update request edits
 * @property {number} segmentHouseNumbers - Number of segment house number edits
 */

/**
 * @typedef {Object} UserProfile
 * @property {number} totalEditCount - Total number of edits made by user
 * @property {number[]} dailyEditCount - Array of daily edit counts
 * @property {EditCountByType} editCountByType - Breakdown of edits by type
 */

/**
 * @typedef {Object} ScriptSettings
 * @property {boolean} Enabled - Whether the script functionality is enabled
 */

/**
 * @typedef {Object} WmeSelection
 * @property {string} objectType - Type of selected object (e.g., 'segment')
 * @property {number[]} ids - Array of selected object IDs
 */

/**
 * @typedef {Object} WmeSegment
 * @property {number} id - Segment ID
 * @property {Object} geometry - Segment geometry data
 */

/**
 * @typedef {Object} MapFeature
 * @property {string} id - Feature ID
 * @property {string} type - Feature type (always 'Feature')
 * @property {Object} geometry - Feature geometry
 * @property {Object} properties - Feature properties
 */

(function () {
  'use strict';

  /** @type {string} Script name from UserScript metadata */
  const SCRIPT_NAME = GM_info.script.name;

  /** @type {string} Local storage key for persisting settings */
  const STORAGE_KEY = 'wazedev_Settings';

  /** @type {string} Map layer name for selection highlighting */
  const SELECTED_LAYER_NAME = 'wazedev_SelectedLayer';

  /** @type {Object|null} WME SDK instance - initialized during bootstrap */
  let wmeSDK = null;

  /** @type {ScriptSettings} Global settings object */
  let settings = {};

  /** @type {Function|null} Event subscription cleanup function */
  let selectionSubscription = null;

  // Initialize the script when SDK is ready

  if (window.SDK_INITIALIZED) {
    console.log(`${SCRIPT_NAME}: SDK initialized...`);

    window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
      console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
    });
  } else {
    console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
  }

  /**
   * Initializes the WME SDK and starts the bootstrap process
   * @throws {Error} When SDK initialization fails
   */
  function bootstrap() {
    try {
      wmeSDK = getWmeSdk({
        scriptId: SCRIPT_NAME.replaceAll(' ', ''),
        scriptName: SCRIPT_NAME,
      });

      Promise.all([wmeReady()])
        .then(() => {
          console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
          init();
        })
        .catch((error) => {
          console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
        });
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
    }
  }

  /**
   * Waits for WME to be fully ready before proceeding
   * @returns {Promise<void>} Promise that resolves when WME is ready
   */
  function wmeReady() {
    return new Promise((resolve) => {
      if (wmeSDK.State.isReady()) {
        console.log(`${SCRIPT_NAME}: WME is ready.`);
        resolve();
      } else {
        console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
        wmeSDK.Events.once({ eventName: 'wme-ready' })
          .then(() => {
            console.log(`${SCRIPT_NAME}: WME is ready now.`);
            resolve();
          })
          .catch((error) => {
            console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
          });
      }
    });
  }

  /**
   * Initializes the script UI and registers the sidebar tab
   * Creates the HTML structure for the script interface
   */
  function init() {
    console.log(`${SCRIPT_NAME}: Script initialized successfully!`);

    // Build HTML content for our tab
    /** @type {HTMLDivElement} */
    const section = document.createElement('div');
    section.innerHTML = `
    <div>
        <h2>Our First Script!</h2>
        <section>
            <input type="checkbox" id="wazedevEnabled" class="wazedevSettingsCheckbox">
            <label for="wazedevEnabled">Enable WazeDev Script</label>
        </section>
        <hr>
        <section>
            <h3>User Info</h3>
            Username: <a href="#" id="wazedevUsernameLink" target="_blank"><span id="wazedevUsername"></span></a><br>
            Rank: <span id="wazedevRank"></span><br>
            Area manager: <span id="wazedevAM"></span><br>
            Country manager: <span id="wazedevCM"></span>
        </section>
        <hr>
        <section>
            <h3>Detailed Profile</h3>
            <div id="wazedevProfileLoading">Loading profile data...</div>
            <div id="wazedevProfileData" style="display: none;">
                Total Edits: <span id="wazedevTotalEdits"></span><br>
                Today's Edits: <span id="wazedevTodayEdits"></span>
            </div>
        </section>
        <hr>
        <section id="wazedevEditsByType" style="display: none;">
            <h3>Edits by Type</h3>
            Segments: <span id="wazedevSegments"></span><br>
            Venues: <span id="wazedevVenues"></span><br>
            Map Problems: <span id="wazedevMapProblems"></span><br>
            Update Requests: <span id="wazedevUpdateRequests"></span><br>
            Place Update Requests: <span id="wazedevPlaceUpdateRequests"></span><br>
            Segment House Numbers: <span id="wazedevSegmentHouseNumbers"></span>
        </section>
    </div>
`;

    // Register the script tab using the SDK
    wmeSDK.Sidebar.registerScriptTab()
      .then(({ tabLabel, tabPane }) => {
        tabLabel.textContent = 'WazeDev';
        tabLabel.title = SCRIPT_NAME;
        tabPane.appendChild(section);

        // Initialize the main() function after tab is created
        main();
      })
      .catch((error) => {
        console.error(`${SCRIPT_NAME}: Error creating script tab`, error);
      });
  }

  /**
   * Saves current settings to localStorage
   * @throws {Error} When localStorage is unavailable or quota exceeded
   */
  function saveSettings() {
    try {
      /** @type {ScriptSettings} */
      const localSettings = { Enabled: settings.Enabled };
      localStorage.setItem(STORAGE_KEY, JSON.stringify(localSettings));
      console.log(`${SCRIPT_NAME}: Settings saved`, localSettings);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error saving settings:`, error);
    }
  }

  /**
   * Loads settings from localStorage and applies defaults for missing properties
   * @returns {void}
   */
  function loadSettings() {
    try {
      /** @type {ScriptSettings|null} */
      const loadedSettings = JSON.parse(localStorage.getItem(STORAGE_KEY));

      /** @type {ScriptSettings} */
      const defaultSettings = { Enabled: false };

      settings = loadedSettings ? loadedSettings : defaultSettings;

      // Add missing default settings
      for (const prop in defaultSettings) {
        if (!settings.hasOwnProperty(prop)) {
          settings[prop] = defaultSettings[prop];
        }
      }
      console.log(`${SCRIPT_NAME}: Settings loaded`, settings);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error loading settings:`, error);
      settings = { Enabled: false };
    }
  }

  /**
   * Sets the checked state of a checkbox element
   * @param {string} checkboxId - The ID of the checkbox element to modify
   * @param {boolean} checked - Whether the checkbox should be checked
   * @returns {void}
   */
  function setChecked(checkboxId, checked) {
    /** @type {HTMLInputElement|null} */
    const checkbox = document.getElementById(checkboxId);
    if (checkbox) checkbox.checked = checked;
  }

  /**
   * Sets up event listeners for all settings checkboxes
   * Automatically handles setting updates and persistence
   * @returns {void}
   */
  function setupEventHandlers() {
    // Find all checkboxes with the settings class in the DOM
    /** @type {NodeListOf<HTMLInputElement>} */
    const checkboxes = document.querySelectorAll('.wazedevSettingsCheckbox');

    // For each settings checkbox, add a change event listener
    checkboxes.forEach((checkbox) => {
      checkbox.addEventListener('change', function () {
        // Extract the setting name from the checkbox ID by removing the 'wazedev' prefix
        // Example: 'wazedevEnabled' -> 'Enabled'
        /** @type {string} */
        const settingName = this.id.substring(7);

        // Update the corresponding value in the settings object
        settings[settingName] = this.checked;

        // Save the updated settings (persist changes, e.g., to storage)
        saveSettings();

        // If the 'Enabled' setting changed, trigger additional actions to update highlighting
        if (settingName === 'Enabled') {
          onEnabledCheckboxChanged(this.checked);
        }
      });
    });
  }

  /**
   * Enables selection highlighting by subscribing to selection change events
   * Safely cleans up any existing subscription before creating a new one
   * @returns {void}
   */
  function enableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // Safely cleanup previous before subscribing!
      selectionSubscription = null;
    }
    selectionSubscription = wmeSDK.Events.on({
      eventName: 'wme-selection-changed',
      eventHandler: drawSelectionHighlight,
    });
  }

  /**
   * Disables selection highlighting by unregistering event handler and clearing highlights
   * @returns {void}
   */
  function disableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // This unregisters the event handler!
      selectionSubscription = null;
    }
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
  }

  /**
   * Draws highlight overlay for currently selected segments
   * Only highlights segments when the script is enabled
   * @returns {void}
   */
  function drawSelectionHighlight() {
    // Check if the script's "Enable" setting is ON
    if (!settings.Enabled) {
      wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
      return;
    }

    // Get the current selection object from the SDK
    /** @type {WmeSelection|null} */
    const selection = wmeSDK.Editing.getSelection();

    // Always remove all previous features from our layer
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });

    // Check if there are any segments actively selected in the selection object
    if (!selection || !selection.ids || selection.objectType !== 'segment') return;

    // Add the selected segments as features to the layer
    selection.ids.forEach((segmentId) => {
      /** @type {WmeSegment|null} */
      const segment = wmeSDK.DataModel.Segments.getById({ segmentId });

      if (segment && segment.geometry) {
        /** @type {MapFeature} */
        const feature = {
          id: `highlighted-${segmentId}`,
          type: 'Feature',
          geometry: segment.geometry,
          properties: {},
        };

        wmeSDK.Map.addFeatureToLayer({
          layerName: SELECTED_LAYER_NAME,
          feature: feature,
        });
      }
    });
  }

  /**
   * Handles changes to the main "Enabled" checkbox
   * Toggles selection highlighting functionality based on the new state
   * @param {boolean} value - The new enabled state (currently unused, reads from settings)
   * @returns {void}
   */
  function onEnabledCheckboxChanged(value) {
    if (settings.Enabled) {
      enableSelectionHighlighting();
      drawSelectionHighlight();
    } else {
      disableSelectionHighlighting();
    }
  }

  /**
   * Main initialization function that sets up the complete user interface
   * Loads settings, populates user information, fetches profile data, and initializes map layer
   * @async
   * @returns {Promise<void>} Promise that resolves when initialization is complete
   */
  async function main() {
    // Load saved settings first
    loadSettings();

    // Set the default state of the "Enable" checkbox at script startup
    setChecked('wazedevEnabled', settings.Enabled);

    // Continue with user info/profile update
    /** @type {UserSession} */
    const userSession = wmeSDK.State.getUserInfo();

    // Set basic user info in the UI
    /** @type {HTMLElement|null} */
    const usernameElement = document.getElementById('wazedevUsername');
    /** @type {HTMLElement|null} */
    const rankElement = document.getElementById('wazedevRank');
    /** @type {HTMLElement|null} */
    const amElement = document.getElementById('wazedevAM');
    /** @type {HTMLElement|null} */
    const cmElement = document.getElementById('wazedevCM');

    if (usernameElement) usernameElement.textContent = userSession.userName;
    if (rankElement) rankElement.textContent = userSession.rank.toString();
    if (amElement) amElement.textContent = userSession.isAreaManager ? 'Yes' : 'No';
    if (cmElement) cmElement.textContent = userSession.isCountryManager ? 'Yes' : 'No';

    // Set profile link
    /** @type {string} */
    const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
    /** @type {HTMLAnchorElement|null} */
    const profileLinkElement = document.getElementById('wazedevUsernameLink');
    if (profileLinkElement) profileLinkElement.href = profileLink;

    // Fetch detailed user profile
    try {
      /** @type {UserProfile} */
      const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });

      // Hide loading indicator and show profile data
      /** @type {HTMLElement|null} */
      const loadingElement = document.getElementById('wazedevProfileLoading');
      /** @type {HTMLElement|null} */
      const profileDataElement = document.getElementById('wazedevProfileData');
      /** @type {HTMLElement|null} */
      const editsByTypeElement = document.getElementById('wazedevEditsByType');

      if (loadingElement) loadingElement.style.display = 'none';
      if (profileDataElement) profileDataElement.style.display = 'block';
      if (editsByTypeElement) editsByTypeElement.style.display = 'block';

      // Update total and today's edit counts
      /** @type {HTMLElement|null} */
      const totalEditsElement = document.getElementById('wazedevTotalEdits');
      /** @type {HTMLElement|null} */
      const todayEditsElement = document.getElementById('wazedevTodayEdits');

      if (totalEditsElement) {
        totalEditsElement.textContent = userProfile.totalEditCount.toLocaleString();
      }

      /** @type {number} */
      const todayEdits = userProfile.dailyEditCount[userProfile.dailyEditCount.length - 1] || 0;
      if (todayEditsElement) {
        todayEditsElement.textContent = todayEdits.toString();
      }

      // Update edit counts by type
      /** @type {EditCountByType} */
      const editTypes = userProfile.editCountByType;

      /** @type {Array<{elementId: string, count: number}>} */
      const editTypeMapping = [
        { elementId: 'wazedevSegments', count: editTypes.segments },
        { elementId: 'wazedevVenues', count: editTypes.venues },
        { elementId: 'wazedevMapProblems', count: editTypes.mapProblems },
        { elementId: 'wazedevUpdateRequests', count: editTypes.updateRequests },
        { elementId: 'wazedevPlaceUpdateRequests', count: editTypes.placeUpdateRequests },
        { elementId: 'wazedevSegmentHouseNumbers', count: editTypes.segmentHouseNumbers },
      ];

      editTypeMapping.forEach(({ elementId, count }) => {
        /** @type {HTMLElement|null} */
        const element = document.getElementById(elementId);
        if (element) element.textContent = count.toLocaleString();
      });

      console.log(`${SCRIPT_NAME}: Profile data loaded successfully`);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error fetching user profile:`, error);
      /** @type {HTMLElement|null} */
      const loadingElement = document.getElementById('wazedevProfileLoading');
      if (loadingElement) {
        loadingElement.textContent = 'Error loading profile data';
      }
    }

    // Setup event handlers for checkboxes
    setupEventHandlers();

    // Add the Layer to the Map with styling configuration
    wmeSDK.Map.addLayer({
      layerName: SELECTED_LAYER_NAME,
      styleRules: [
        {
          style: {
            strokeColor: 'red', // Highlight color (red)
            strokeWidth: 12, // Thickness (12px)
            strokeLinecap: 'round', // Rounded ends
            fillOpacity: 0, // No fill needed for lines
          },
        },
      ],
    });

    // Activate script effect if previously enabled
    onEnabledCheckboxChanged(settings.Enabled);

    console.log(`${SCRIPT_NAME}: Settings initialized!`);
  }
})();
⚠️ **GitHub.com Fallback** ⚠️