Files
Ivan Starkov 67892b0690 fix: Identifier names must not be reserved words (#4221)
## Description

- [x] todo use
https://github.com/sindresorhus/reserved-identifiers/blob/main/index.js

## Steps for reproduction

1. click button
2. expect xyz

## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env` file
2024-10-05 21:16:14 +03:00

75 lines
2.0 KiB
TypeScript

import reservedIdentifiers from "reserved-identifiers";
const identifiers = reservedIdentifiers({ includeGlobalProperties: true });
const isReserved = (identifier: string) => identifiers.has(identifier);
export type Scope = {
/**
* Accepts unique id to identify specific variable
* and preferred name to use it as variable name
* or suffix if already used.
*/
getName(id: string, preferredName: string): string;
};
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
const normalizeJsName = (name: string) => {
// only letters, digits, underscores and dollar signs allowed in names
// delete everything else
name = name.replaceAll(/[^\w$]/g, "");
// put at least single underscore in name to avoid syntax error
if (name.length === 0) {
return "_";
}
// variable name can start with letter, underscore and dollar signs
if (/[A-Za-z_$]/.test(name[0]) === false) {
name = `_${name}`;
}
if (isReserved(name)) {
return `${name}_`;
}
return name;
};
/**
* Utility to maintain unique variable when generate code.
* Single scope is shared for generated module for simplicity.
*
* occupiedIdentifiers parameter prevents collision with hardcoded
* identifiers.
*/
export const createScope = (
occupiedIdentifiers: string[] = [],
normalizeName = normalizeJsName,
separator = "_"
): Scope => {
const nameById = new Map<string, string>();
const usedNames = new Set<string>();
for (const identifier of occupiedIdentifiers) {
usedNames.add(identifier);
}
const getName = (id: string, preferredName: string) => {
const cachedName = nameById.get(id);
if (cachedName !== undefined) {
return cachedName;
}
preferredName = normalizeName(preferredName);
let index = 0;
let scopedName = preferredName;
while (usedNames.has(scopedName)) {
index += 1;
scopedName = `${preferredName}${separator}${index}`;
}
nameById.set(id, scopedName);
usedNames.add(scopedName);
return scopedName;
};
return {
getName,
};
};