Final vsCode with JSDOC - WazeDev/WazeDev.github.io GitHub Wiki
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
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
Here are some examples showing different JSDoc patterns used in our WazeDev script:
/**
* Draws highlight overlay for currently selected segments
* Only highlights segments when the script is enabled
* @returns {void}
*/
function drawSelectionHighlight() {
// ... function code as before ...
}/**
* 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;
}/**
* Global settings object containing user preferences
* @type {Object}
* @property {boolean} Enabled - Whether the script highlighting is active
*/
let settings = {};/**
* 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
}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
| 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!
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
// ==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!`);
}
})();