Skip to content

Commit 414c64f

Browse files
committed
Extracted code from mrc_class_method_def functions findLegalMethodName and isMethodNameUsed to make function makeLegalName in validator.ts.
Updated mrc_class_method_def, mrc_component, mrc_event, and mrc_mechanism blocks to use makeLegalName.
1 parent 80b3cf3 commit 414c64f

File tree

5 files changed

+112
-65
lines changed

5 files changed

+112
-65
lines changed

src/blocks/mrc_class_method_def.ts

Lines changed: 17 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import * as toolboxItems from '../toolbox/items';
3535
import { getClassData } from './utils/python';
3636
import { FunctionData } from './utils/python_json_types';
3737
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
38+
import { makeLegalName } from './utils/validator';
3839
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
3940
import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
4041
import * as paramContainer from './mrc_param_container'
@@ -285,10 +286,23 @@ const CLASS_METHOD_DEF = {
285286
// When the user changes the method name on the block, clear the mrcPythonMethodName field.
286287
this.mrcPythonMethodName = '';
287288

288-
// Strip leading and trailing whitespace.
289-
name = name.trim();
289+
if (this.isInFlyout) {
290+
// Flyouts can have multiple methods with identical names.
291+
return name;
292+
}
293+
294+
const otherNames: string[] = [];
295+
this.workspace.getBlocksByType(BLOCK_NAME)
296+
.filter(block => block.id !== this.id)
297+
.forEach((block) => {
298+
otherNames.push(block.getFieldValue(FIELD_METHOD_NAME));
299+
const classMethodDefBlock = block as ClassMethodDefBlock;
300+
if (classMethodDefBlock.mrcPythonMethodName) {
301+
otherNames.push(classMethodDefBlock.mrcPythonMethodName);
302+
}
303+
});
290304

291-
const legalName = findLegalMethodName(name, this);
305+
const legalName = makeLegalName(name, otherNames, /* mustBeginWithLetter */ true);
292306
const oldName = nameField.getValue();
293307
if (oldName && oldName !== name && oldName !== legalName) {
294308
// Rename any callers.
@@ -366,61 +380,6 @@ const CLASS_METHOD_DEF = {
366380
},
367381
};
368382

369-
/**
370-
* Ensure two identically-named methods don't exist.
371-
* Take the proposed method name, and return a legal name i.e. one that
372-
* is not empty and doesn't collide with other methods.
373-
*
374-
* @param name Proposed method name.
375-
* @param block Block to disambiguate.
376-
* @returns Non-colliding name.
377-
*/
378-
function findLegalMethodName(name: string, block: ClassMethodDefBlock): string {
379-
if (block.isInFlyout) {
380-
// Flyouts can have multiple methods called 'my_method'.
381-
return name;
382-
}
383-
name = name || 'unnamed';
384-
while (isMethodNameUsed(name, block.workspace, block)) {
385-
// Collision with another method.
386-
const r = name.match(/^(.*?)(\d+)$/);
387-
if (!r) {
388-
name += '2';
389-
} else {
390-
name = r[1] + (parseInt(r[2]) + 1);
391-
}
392-
}
393-
return name;
394-
}
395-
396-
/**
397-
* Return if the given name is already a method name.
398-
*
399-
* @param name The questionable name.
400-
* @param workspace The workspace to scan for collisions.
401-
* @param opt_exclude Optional block to exclude from comparisons (one doesn't
402-
* want to collide with oneself).
403-
* @returns True if the name is used, otherwise return false.
404-
*/
405-
function isMethodNameUsed(
406-
name: string, workspace: Blockly.Workspace, opt_exclude?: Blockly.Block): boolean {
407-
const nameLowerCase = name.toLowerCase();
408-
for (const block of workspace.getBlocksByType(BLOCK_NAME)) {
409-
if (block === opt_exclude) {
410-
continue;
411-
}
412-
if (nameLowerCase === block.getFieldValue(FIELD_METHOD_NAME).toLowerCase()) {
413-
return true;
414-
}
415-
const classMethodDefBlock = block as ClassMethodDefBlock;
416-
if (classMethodDefBlock.mrcPythonMethodName &&
417-
nameLowerCase === classMethodDefBlock.mrcPythonMethodName.toLowerCase()) {
418-
return true;
419-
}
420-
}
421-
return false;
422-
}
423-
424383
export const setup = function () {
425384
Blockly.Blocks[BLOCK_NAME] = CLASS_METHOD_DEF;
426385
};

src/blocks/mrc_component.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { Editor } from '../editor/editor';
2828
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2929
import { getModuleTypeForWorkspace } from './utils/workspaces';
3030
import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './utils/python';
31+
import { makeLegalName } from './utils/validator';
3132
import * as toolboxItems from '../toolbox/items';
3233
import * as storageModule from '../storage/module';
3334
import * as storageModuleContent from '../storage/module_content';
@@ -164,10 +165,19 @@ const COMPONENT = {
164165
}
165166
},
166167
mrcNameFieldValidator(this: ComponentBlock, nameField: Blockly.FieldTextInput, name: string): string {
167-
// Strip leading and trailing whitespace.
168-
name = name.trim();
168+
if (this.isInFlyout) {
169+
// Flyouts can have multiple methods with identical names.
170+
return name;
171+
}
172+
173+
const otherNames: string[] = [];
174+
this.workspace.getBlocksByType(BLOCK_NAME)
175+
.filter(block => block.id !== this.id)
176+
.forEach((block) => {
177+
otherNames.push(block.getFieldValue(FIELD_NAME));
178+
});
169179

170-
const legalName = name;
180+
const legalName = makeLegalName(name, otherNames, /* mustBeginWithLetter */ true);
171181
const oldName = nameField.getValue();
172182
if (oldName && oldName !== name && oldName !== legalName) {
173183
// Rename any callers.

src/blocks/mrc_event.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import * as Blockly from 'blockly';
2323

2424
import { MRC_STYLE_EVENTS } from '../themes/styles'
2525
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
26+
import { makeLegalName } from './utils/validator';
2627
import { Parameter } from './mrc_class_method_def';
2728
import { Editor } from '../editor/editor';
2829
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
@@ -185,10 +186,19 @@ const EVENT = {
185186
});
186187
},
187188
mrcNameFieldValidator(this: EventBlock, nameField: Blockly.FieldTextInput, name: string): string {
188-
// Strip leading and trailing whitespace.
189-
name = name.trim();
189+
if (this.isInFlyout) {
190+
// Flyouts can have multiple methods with identical names.
191+
return name;
192+
}
193+
194+
const otherNames: string[] = [];
195+
this.workspace.getBlocksByType(BLOCK_NAME)
196+
.filter(block => block.id !== this.id)
197+
.forEach((block) => {
198+
otherNames.push(block.getFieldValue(FIELD_EVENT_NAME));
199+
});
190200

191-
const legalName = name;
201+
const legalName = makeLegalName(name, otherNames, /* mustBeginWithLetter */ false);
192202
const oldName = nameField.getValue();
193203
if (oldName && oldName !== name && oldName !== legalName) {
194204
// Rename any callers.

src/blocks/mrc_mechanism.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
2727
import { Editor } from '../editor/editor';
2828
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2929
import { getAllowedTypesForSetCheck } from './utils/python';
30+
import { makeLegalName } from './utils/validator';
3031
import * as toolboxItems from '../toolbox/items';
3132
import * as storageModule from '../storage/module';
3233
import * as storageModuleContent from '../storage/module_content';
@@ -167,7 +168,19 @@ const MECHANISM = {
167168
// Strip leading and trailing whitespace.
168169
name = name.trim();
169170

170-
const legalName = name;
171+
if (this.isInFlyout) {
172+
// Flyouts can have multiple methods with identical names.
173+
return name;
174+
}
175+
176+
const otherNames: string[] = [];
177+
this.workspace.getBlocksByType(BLOCK_NAME)
178+
.filter(block => block.id !== this.id)
179+
.forEach((block) => {
180+
otherNames.push(block.getFieldValue(FIELD_NAME));
181+
});
182+
183+
const legalName = makeLegalName(name, otherNames, /* mustBeginWithLetter */ true);
171184
const oldName = nameField.getValue();
172185
if (oldName && oldName !== name && oldName !== legalName) {
173186
// Rename any callers.

src/blocks/utils/validator.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* @author [email protected] (Liz Looney)
20+
*/
21+
22+
/**
23+
* Take the proposed name, and return a legal name.
24+
* A legal name is:
25+
* 1. not empty.
26+
* 2. doesn't collide with other names.
27+
* 3. Optional: begins with a letter.
28+
*/
29+
export function makeLegalName(proposedName: string, otherNames: string[], mustBeginWithLetter: boolean): string {
30+
const otherNamesLowerCase = otherNames.map(n => n.toLowerCase());
31+
32+
// Strip leading and trailing whitespace.
33+
let name = proposedName.trim();
34+
35+
// Make the name non-empty.
36+
name = name || 'unnamed';
37+
38+
if (mustBeginWithLetter) {
39+
// If the name begins with a non-alphabetic character, insert the letter a at the beginning.
40+
if (!name.match(/^[a-zA-Z].*$/)) {
41+
name = 'a' + name;
42+
}
43+
}
44+
45+
while (otherNamesLowerCase.includes(name.toLowerCase())) {
46+
// Collision with another name.
47+
const r = name.match(/^(.*?)(\d+)$/);
48+
if (!r) {
49+
name += '2';
50+
} else {
51+
name = r[1] + (parseInt(r[2]) + 1);
52+
}
53+
}
54+
return name;
55+
}

0 commit comments

Comments
 (0)