mirror of
https://github.com/google/blockly.git
synced 2025-12-15 22:00:07 +01:00
* chore: add configuration for api extractor * fix: remove extra param names * chore: private to internal * remove unrestricted * chore: remove double backticks * chore: remove fileoverview and export * as * chore: return to returns * chore: fix backslashes and angle brackets in tsdoc * chore: final to sealed * chore: ignore to internal * chore: fix link tags * chore: add api-extractor configuration * chore: add unrecognized tag names * chore: remove tsdoc-metadata * fix: correct index.d.ts * chore: fix connection link
274 lines
8.8 KiB
TypeScript
274 lines
8.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2012 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Utility functions for handling variable and procedure names.
|
|
*
|
|
* @class
|
|
*/
|
|
import * as goog from '../closure/goog/goog.js';
|
|
goog.declareModuleId('Blockly.Names');
|
|
|
|
import {Msg} from './msg.js';
|
|
// import * as Procedures from './procedures.js';
|
|
import type {VariableMap} from './variable_map.js';
|
|
import * as Variables from './variables.js';
|
|
import type {Workspace} from './workspace.js';
|
|
|
|
|
|
/**
|
|
* Class for a database of entity names (variables, procedures, etc).
|
|
*
|
|
* @alias Blockly.Names
|
|
*/
|
|
export class Names {
|
|
static DEVELOPER_VARIABLE_TYPE: NameType;
|
|
private readonly variablePrefix_: string;
|
|
|
|
/** A set of reserved words. */
|
|
private readonly reservedWords: Set<string>;
|
|
|
|
/**
|
|
* A map from type (e.g. name, procedure) to maps from names to generated
|
|
* names.
|
|
*/
|
|
private readonly db = new Map<string, Map<string, string>>();
|
|
|
|
/** A set of used names to avoid collisions. */
|
|
private readonly dbReverse = new Set<string>();
|
|
|
|
/**
|
|
* The variable map from the workspace, containing Blockly variable models.
|
|
*/
|
|
private variableMap_: VariableMap|null = null;
|
|
|
|
/**
|
|
* @param reservedWordsList A comma-separated string of words that are illegal
|
|
* for use as names in a language (e.g. 'new,if,this,...').
|
|
* @param opt_variablePrefix Some languages need a '$' or a namespace before
|
|
* all variable names (but not procedure names).
|
|
*/
|
|
constructor(reservedWordsList: string, opt_variablePrefix?: string) {
|
|
/** The prefix to attach to variable names in generated code. */
|
|
this.variablePrefix_ = opt_variablePrefix || '';
|
|
|
|
this.reservedWords =
|
|
new Set<string>(reservedWordsList ? reservedWordsList.split(',') : []);
|
|
}
|
|
|
|
/**
|
|
* Empty the database and start from scratch. The reserved words are kept.
|
|
*/
|
|
reset() {
|
|
this.db.clear();
|
|
this.dbReverse.clear();
|
|
this.variableMap_ = null;
|
|
}
|
|
|
|
/**
|
|
* Set the variable map that maps from variable name to variable object.
|
|
*
|
|
* @param map The map to track.
|
|
*/
|
|
setVariableMap(map: VariableMap) {
|
|
this.variableMap_ = map;
|
|
}
|
|
|
|
/**
|
|
* Get the name for a user-defined variable, based on its ID.
|
|
* This should only be used for variables of NameType VARIABLE.
|
|
*
|
|
* @param id The ID to look up in the variable map.
|
|
* @returns The name of the referenced variable, or null if there was no
|
|
* variable map or the variable was not found in the map.
|
|
*/
|
|
private getNameForUserVariable_(id: string): string|null {
|
|
if (!this.variableMap_) {
|
|
console.warn(
|
|
'Deprecated call to Names.prototype.getName without ' +
|
|
'defining a variable map. To fix, add the following code in your ' +
|
|
'generator\'s init() function:\n' +
|
|
'Blockly.YourGeneratorName.nameDB_.setVariableMap(' +
|
|
'workspace.getVariableMap());');
|
|
return null;
|
|
}
|
|
const variable = this.variableMap_.getVariableById(id);
|
|
if (variable) {
|
|
return variable.name;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Generate names for user variables, but only ones that are being used.
|
|
*
|
|
* @param workspace Workspace to generate variables from.
|
|
*/
|
|
populateVariables(workspace: Workspace) {
|
|
const variables = Variables.allUsedVarModels(workspace);
|
|
for (let i = 0; i < variables.length; i++) {
|
|
this.getName(variables[i].getId(), NameType.VARIABLE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate names for procedures.
|
|
*
|
|
* @param workspace Workspace to generate procedures from.
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
populateProcedures(workspace: Workspace) {
|
|
throw new Error(
|
|
'The implementation of populateProcedures should be ' +
|
|
'monkey-patched in by blockly.ts');
|
|
}
|
|
|
|
/**
|
|
* Convert a Blockly entity name to a legal exportable entity name.
|
|
*
|
|
* @param nameOrId The Blockly entity name (no constraints) or variable ID.
|
|
* @param type The type of the name in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns An entity name that is legal in the exported language.
|
|
*/
|
|
getName(nameOrId: string, type: NameType|string): string {
|
|
let name = nameOrId;
|
|
if (type === NameType.VARIABLE) {
|
|
const varName = this.getNameForUserVariable_(nameOrId);
|
|
if (varName) {
|
|
// Successful ID lookup.
|
|
name = varName;
|
|
}
|
|
}
|
|
const normalizedName = name.toLowerCase();
|
|
|
|
const isVar =
|
|
type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE;
|
|
|
|
const prefix = isVar ? this.variablePrefix_ : '';
|
|
if (!this.db.has(type)) {
|
|
this.db.set(type, new Map<string, string>());
|
|
}
|
|
const typeDb = this.db.get(type);
|
|
if (typeDb!.has(normalizedName)) {
|
|
return prefix + typeDb!.get(normalizedName);
|
|
}
|
|
const safeName = this.getDistinctName(name, type);
|
|
typeDb!.set(normalizedName, safeName.substr(prefix.length));
|
|
return safeName;
|
|
}
|
|
|
|
/**
|
|
* Return a list of all known user-created names of a specified name type.
|
|
*
|
|
* @param type The type of entity in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns A list of Blockly entity names (no constraints).
|
|
*/
|
|
getUserNames(type: NameType|string): string[] {
|
|
const userNames = this.db.get(type)?.keys();
|
|
return userNames ? Array.from(userNames) : [];
|
|
}
|
|
|
|
/**
|
|
* Convert a Blockly entity name to a legal exportable entity name.
|
|
* Ensure that this is a new name not overlapping any previously defined name.
|
|
* Also check against list of reserved words for the current language and
|
|
* ensure name doesn't collide.
|
|
*
|
|
* @param name The Blockly entity name (no constraints).
|
|
* @param type The type of entity in Blockly ('VARIABLE', 'PROCEDURE',
|
|
* 'DEVELOPER_VARIABLE', etc...).
|
|
* @returns An entity name that is legal in the exported language.
|
|
*/
|
|
getDistinctName(name: string, type: NameType|string): string {
|
|
let safeName = this.safeName_(name);
|
|
let i = '';
|
|
while (this.dbReverse.has(safeName + i) ||
|
|
this.reservedWords.has(safeName + i)) {
|
|
// Collision with existing name. Create a unique name.
|
|
// AnyDuringMigration because: Type 'string | 2' is not assignable to
|
|
// type 'string'.
|
|
i = (i ? i + 1 : 2) as AnyDuringMigration;
|
|
}
|
|
safeName += i;
|
|
this.dbReverse.add(safeName);
|
|
const isVar =
|
|
type === NameType.VARIABLE || type === NameType.DEVELOPER_VARIABLE;
|
|
const prefix = isVar ? this.variablePrefix_ : '';
|
|
return prefix + safeName;
|
|
}
|
|
|
|
/**
|
|
* Given a proposed entity name, generate a name that conforms to the
|
|
* [_A-Za-z][_A-Za-z0-9]* format that most languages consider legal for
|
|
* variable and function names.
|
|
*
|
|
* @param name Potentially illegal entity name.
|
|
* @returns Safe entity name.
|
|
*/
|
|
private safeName_(name: string): string {
|
|
if (!name) {
|
|
name = Msg['UNNAMED_KEY'] || 'unnamed';
|
|
} else {
|
|
// Unfortunately names in non-latin characters will look like
|
|
// _E9_9F_B3_E4_B9_90 which is pretty meaningless.
|
|
// https://github.com/google/blockly/issues/1654
|
|
name = encodeURI(name.replace(/ /g, '_')).replace(/[^\w]/g, '_');
|
|
// Most languages don't allow names with leading numbers.
|
|
if ('0123456789'.indexOf(name[0]) !== -1) {
|
|
name = 'my_' + name;
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Do the given two entity names refer to the same entity?
|
|
* Blockly names are case-insensitive.
|
|
*
|
|
* @param name1 First name.
|
|
* @param name2 Second name.
|
|
* @returns True if names are the same.
|
|
*/
|
|
static equals(name1: string, name2: string): boolean {
|
|
// name1.localeCompare(name2) is slower.
|
|
return name1.toLowerCase() === name2.toLowerCase();
|
|
}
|
|
}
|
|
|
|
export namespace Names {
|
|
/**
|
|
* Enum for the type of a name. Different name types may have different rules
|
|
* about collisions.
|
|
* When JavaScript (or most other languages) is generated, variable 'foo' and
|
|
* procedure 'foo' would collide. However, Blockly has no such problems since
|
|
* variable get 'foo' and procedure call 'foo' are unambiguous.
|
|
* Therefore, Blockly keeps a separate name type to disambiguate.
|
|
* getName('foo', 'VARIABLE') = 'foo'
|
|
* getName('foo', 'PROCEDURE') = 'foo2'
|
|
*
|
|
* @alias Blockly.Names.NameType
|
|
*/
|
|
export enum NameType {
|
|
DEVELOPER_VARIABLE = 'DEVELOPER_VARIABLE',
|
|
VARIABLE = 'VARIABLE',
|
|
PROCEDURE = 'PROCEDURE',
|
|
}
|
|
}
|
|
|
|
export type NameType = Names.NameType;
|
|
export const NameType = Names.NameType;
|
|
|
|
/**
|
|
* Constant to separate developer variable names from user-defined variable
|
|
* names when running generators.
|
|
* A developer variable will be declared as a global in the generated code, but
|
|
* will never be shown to the user in the workspace or stored in the variable
|
|
* map.
|
|
*/
|
|
Names.DEVELOPER_VARIABLE_TYPE = NameType.DEVELOPER_VARIABLE;
|