@@ -4,16 +4,15 @@ import { on } from '@ember/modifier';
4
4
import { action } from ' @ember/object' ;
5
5
import type Owner from ' @ember/owner' ;
6
6
import { service } from ' @ember/service' ;
7
- import { capitalize } from ' @ember/string' ;
8
7
import { buildWaiter } from ' @ember/test-waiters' ;
9
8
import Component from ' @glimmer/component' ;
10
9
import { tracked } from ' @glimmer/tracking' ;
11
10
11
+ import camelCase from ' camelcase' ;
12
12
import { restartableTask , enqueueTask } from ' ember-concurrency' ;
13
13
import perform from ' ember-concurrency/helpers/perform' ;
14
14
import focusTrap from ' ember-focus-trap/modifiers/focus-trap' ;
15
15
import onKeyMod from ' ember-keyboard/modifiers/on-key' ;
16
- import camelCase from ' lodash/camelCase' ;
17
16
18
17
import {
19
18
FieldContainer ,
@@ -568,19 +567,14 @@ export default class CreateFileModal extends Component<Signature> {
568
567
let {
569
568
ref : { name : exportName, module },
570
569
} = (this .definitionClass ?? this .selectedCatalogEntry )! ; // we just checked above to make sure one of these exists
571
- let className = camelize (this .displayName );
572
- // make sure we don't collide with a javascript built-in object
573
- if (typeof (globalThis as any )[className ] !== ' undefined' ) {
574
- className = ` ${className }0 ` ;
575
- }
570
+ let className = convertToClassName (this .displayName );
571
+
576
572
let absoluteModule = new URL (module , this .selectedCatalogEntry ?.id );
577
573
let moduleURL = maybeRelativeURL (
578
574
absoluteModule ,
579
575
url ,
580
576
this .selectedRealmURL ,
581
577
);
582
- // sanitize the name since it will be used in javascript code
583
- let safeName = this .displayName .replace (/ [^ A-Za-z \d -_ ] / g , ' ' ).trim ();
584
578
let src: string [] = [];
585
579
586
580
// There is actually only one possible declaration collision: `className` and `parent`,
@@ -590,27 +584,28 @@ export default class CreateFileModal extends Component<Signature> {
590
584
import { ${exportName } as ${exportName }Parent } from '${moduleURL }';
591
585
import { Component } from 'https://cardstack.com/base/card-api';
592
586
export class ${className } extends ${exportName }Parent {
593
- static displayName = "${safeName }"; ` );
587
+ static displayName = "${this . displayName }"; ` );
594
588
} else if (exportName === ' default' ) {
595
- let parent = camelize (
589
+ let parent = camelCase (
596
590
module
597
591
.split (' /' )
598
592
.pop ()!
599
593
.replace (/ \. [^ . ] + $ / , ' ' ),
594
+ { pascalCase: true },
600
595
);
601
596
// check for parent/className declaration collision
602
597
parent = parent === className ? ` ${parent }Parent ` : parent ;
603
598
src .push (`
604
599
import ${parent } from '${moduleURL }';
605
600
import { Component } from 'https://cardstack.com/base/card-api';
606
601
export class ${className } extends ${parent } {
607
- static displayName = "${safeName }"; ` );
602
+ static displayName = "${this . displayName }"; ` );
608
603
} else {
609
604
src .push (`
610
605
import { ${exportName } } from '${moduleURL }';
611
606
import { Component } from 'https://cardstack.com/base/card-api';
612
607
export class ${className } extends ${exportName } {
613
- static displayName = "${safeName }"; ` );
608
+ static displayName = "${this . displayName }"; ` );
614
609
}
615
610
src .push (` \n /*` );
616
611
if (this .fileType .id === ' card-definition' ) {
@@ -730,8 +725,33 @@ export class ${className} extends ${exportName} {
730
725
});
731
726
}
732
727
733
- function camelize(name : string ) {
734
- return capitalize (camelCase (name ));
728
+ export function convertToClassName(input : string ) {
729
+ // \p{L}: a letter
730
+ let invalidLeadingCharactersRemoved = camelCase (
731
+ input .replace (/ ^ [^ \p {L}_$] + / u , ' ' ),
732
+ { pascalCase: true },
733
+ );
734
+
735
+ if (! invalidLeadingCharactersRemoved ) {
736
+ let prefixedInput = ` Class${input } ` ;
737
+ invalidLeadingCharactersRemoved = camelCase (
738
+ prefixedInput .replace (/ ^ [^ \p {L}_$] + / u , ' ' ),
739
+ { pascalCase: true },
740
+ );
741
+ }
742
+
743
+ let className = invalidLeadingCharactersRemoved .replace (
744
+ // \p{N}: a number
745
+ / [^ \p {L}\p {N}_$] + / gu ,
746
+ ' ' ,
747
+ );
748
+
749
+ // make sure we don't collide with a javascript built-in object
750
+ if (typeof (globalThis as any )[className ] !== ' undefined' ) {
751
+ className = ` ${className }0 ` ;
752
+ }
753
+
754
+ return className ;
735
755
}
736
756
737
757
const SelectedTypePill: TemplateOnlyComponent <{
0 commit comments