@alloy-js/csharp 0.23.0-dev.10 → 0.23.0-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/src/components/method/method.test.js +64 -0
- package/dist/dev/src/components/method/method.test.js.map +1 -1
- package/dist/dev/src/identifier-utils.js +45 -0
- package/dist/dev/src/identifier-utils.js.map +1 -0
- package/dist/dev/src/index.js +2 -0
- package/dist/dev/src/index.js.map +1 -1
- package/dist/dev/src/keywords.js +39 -0
- package/dist/dev/src/keywords.js.map +1 -0
- package/dist/dev/src/name-policy.js +29 -6
- package/dist/dev/src/name-policy.js.map +1 -1
- package/dist/dev/src/name-policy.test.js +167 -0
- package/dist/dev/src/name-policy.test.js.map +1 -0
- package/dist/src/components/method/method.test.js +48 -0
- package/dist/src/components/method/method.test.js.map +1 -1
- package/dist/src/identifier-utils.d.ts +22 -0
- package/dist/src/identifier-utils.d.ts.map +1 -0
- package/dist/src/identifier-utils.js +45 -0
- package/dist/src/identifier-utils.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/keywords.d.ts +32 -0
- package/dist/src/keywords.d.ts.map +1 -0
- package/dist/src/keywords.js +39 -0
- package/dist/src/keywords.js.map +1 -0
- package/dist/src/name-policy.d.ts +7 -0
- package/dist/src/name-policy.d.ts.map +1 -1
- package/dist/src/name-policy.js +29 -6
- package/dist/src/name-policy.js.map +1 -1
- package/dist/src/name-policy.test.d.ts +2 -0
- package/dist/src/name-policy.test.d.ts.map +1 -0
- package/dist/src/name-policy.test.js +167 -0
- package/dist/src/name-policy.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/api/contexts/csharp-context.md +21 -0
- package/docs/api/contexts/index.md +3 -0
- package/docs/api/functions/createCSharpNamePolicy.md +4 -0
- package/docs/api/functions/index.md +5 -1
- package/docs/api/functions/isCSharpContextualKeyword.md +20 -0
- package/docs/api/functions/isCSharpKeyword.md +22 -0
- package/docs/api/functions/isValidCSharpIdentifier.md +20 -0
- package/docs/api/functions/sanitizeCSharpIdentifier.md +24 -0
- package/docs/api/index.md +3 -2
- package/docs/api/variables/csharpKeywords.md +11 -0
- package/docs/api/variables/index.md +1 -0
- package/package.json +3 -3
- package/src/components/method/method.test.tsx +36 -0
- package/src/identifier-utils.ts +45 -0
- package/src/index.ts +2 -0
- package/src/keywords.ts +162 -0
- package/src/name-policy.test.ts +210 -0
- package/src/name-policy.ts +30 -6
- package/temp/api.json +237 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# csharp context
|
|
2
|
+
|
|
3
|
+
C# contextual keywords that are reserved in certain contexts. While not always reserved, treating them as keywords in generated code avoids subtle context-dependent issues.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const csharpContextualKeywords: ReadonlySet<string>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Accessor
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const myContext = useContext(csharpContext);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Context interface
|
|
16
|
+
|
|
17
|
+
string
|
|
18
|
+
|
|
19
|
+
## See also
|
|
20
|
+
|
|
21
|
+
* <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/#contextual-keywords>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# createCSharpNamePolicy
|
|
2
2
|
|
|
3
|
+
Creates the C# naming policy with case conversion and keyword escaping.
|
|
4
|
+
|
|
5
|
+
After applying the appropriate case conversion for each element kind, the resulting name is checked against C# reserved and contextual keywords. If it matches (case-sensitively), the name is prefixed with `@`.
|
|
6
|
+
|
|
3
7
|
```ts
|
|
4
8
|
import { createCSharpNamePolicy } from "@alloy-js/csharp";
|
|
5
9
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- [accessibilityFromProps](accessibilityFromProps.md) — import { accessibilityFromProps } from "@alloy-js/csharp";
|
|
5
5
|
- [computeModifiersPrefix](computeModifiersPrefix.md) — Resolve the modifier prefix
|
|
6
6
|
- [createClassScope](createClassScope.md) — import { createClassScope } from "@alloy-js/csharp";
|
|
7
|
-
- [createCSharpNamePolicy](createCSharpNamePolicy.md) —
|
|
7
|
+
- [createCSharpNamePolicy](createCSharpNamePolicy.md) — Creates the C# naming policy with case conversion and keyword escaping.
|
|
8
8
|
- [createCSharpNamespaceScope](createCSharpNamespaceScope.md) — import { createCSharpNamespaceScope } from "@alloy-js/csharp";
|
|
9
9
|
- [createFieldSymbol](createFieldSymbol.md) — import { createFieldSymbol } from "@alloy-js/csharp";
|
|
10
10
|
- [createLibrary](createLibrary.md) — import { createLibrary } from "@alloy-js/csharp";
|
|
@@ -19,9 +19,13 @@
|
|
|
19
19
|
- [createVariableSymbol](createVariableSymbol.md) — import { createVariableSymbol } from "@alloy-js/csharp";
|
|
20
20
|
- [getAccessModifier](getAccessModifier.md) — import { getAccessModifier } from "@alloy-js/csharp";
|
|
21
21
|
- [getAsyncModifier](getAsyncModifier.md) — import { getAsyncModifier } from "@alloy-js/csharp";
|
|
22
|
+
- [isCSharpContextualKeyword](isCSharpContextualKeyword.md) — Returns true if the given name is a C# contextual keyword.
|
|
23
|
+
- [isCSharpKeyword](isCSharpKeyword.md) — Returns true if the given name is a C# reserved keyword.
|
|
24
|
+
- [isValidCSharpIdentifier](isValidCSharpIdentifier.md) — Checks whether the provided name is a valid C# identifier (without `@` prefix).
|
|
22
25
|
- [makeModifiers](makeModifiers.md) — import { makeModifiers } from "@alloy-js/csharp";
|
|
23
26
|
- [nonAccessibilityFromProps](nonAccessibilityFromProps.md) — import { nonAccessibilityFromProps } from "@alloy-js/csharp";
|
|
24
27
|
- [ref](ref.md) — import { ref } from "@alloy-js/csharp";
|
|
28
|
+
- [sanitizeCSharpIdentifier](sanitizeCSharpIdentifier.md) — Transforms an arbitrary string into a valid C# identifier by replacing invalid characters.
|
|
25
29
|
- [useCsharpFormatOptions](useCsharpFormatOptions.md) — import { useCsharpFormatOptions } from "@alloy-js/csharp";
|
|
26
30
|
- [useCSharpNamePolicy](useCSharpNamePolicy.md) — import { useCSharpNamePolicy } from "@alloy-js/csharp";
|
|
27
31
|
- [useCSharpScope](useCSharpScope.md) — import { useCSharpScope } from "@alloy-js/csharp";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# isCSharpContextualKeyword
|
|
2
|
+
|
|
3
|
+
Returns true if the given name is a C# contextual keyword. Contextual keywords are only reserved in specific language contexts and are generally valid as identifiers.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { isCSharpContextualKeyword } from "@alloy-js/csharp";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function isCSharpContextualKeyword(name: string): boolean;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Parameters
|
|
13
|
+
|
|
14
|
+
| | | |
|
|
15
|
+
| ---- | ------ | - |
|
|
16
|
+
| name | string | |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
boolean
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# isCSharpKeyword
|
|
2
|
+
|
|
3
|
+
Returns true if the given name is a C# reserved keyword. The check is case-sensitive, matching C# language semantics.
|
|
4
|
+
|
|
5
|
+
Note: this only checks reserved keywords, not contextual keywords. Contextual keywords are only reserved in specific language contexts and are valid identifiers elsewhere (e.g., `value` is valid as a parameter name). Use [isCSharpContextualKeyword](../iscsharpcontextualkeyword/) to check contextual keywords separately.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { isCSharpKeyword } from "@alloy-js/csharp";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function isCSharpKeyword(name: string): boolean;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Parameters
|
|
15
|
+
|
|
16
|
+
| | | |
|
|
17
|
+
| ---- | ------ | - |
|
|
18
|
+
| name | string | |
|
|
19
|
+
|
|
20
|
+
## Returns
|
|
21
|
+
|
|
22
|
+
boolean
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# isValidCSharpIdentifier
|
|
2
|
+
|
|
3
|
+
Checks whether the provided name is a valid C# identifier (without `@` prefix). Does not account for keyword conflicts — use [isCSharpKeyword](../iscsharpkeyword/) for that.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { isValidCSharpIdentifier } from "@alloy-js/csharp";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
function isValidCSharpIdentifier(name: string): boolean;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Parameters
|
|
13
|
+
|
|
14
|
+
| | | |
|
|
15
|
+
| ---- | ------ | - |
|
|
16
|
+
| name | string | |
|
|
17
|
+
|
|
18
|
+
## Returns
|
|
19
|
+
|
|
20
|
+
boolean true if the name matches C# identifier rules (letter or underscore start, word chars after).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# sanitizeCSharpIdentifier
|
|
2
|
+
|
|
3
|
+
Transforms an arbitrary string into a valid C# identifier by replacing invalid characters. The result may still be a C# keyword — callers should combine with keyword escaping if needed.
|
|
4
|
+
|
|
5
|
+
* If the first character is not a letter or underscore, a `_` prefix is added.
|
|
6
|
+
* Subsequent non-word characters are replaced with `_`.
|
|
7
|
+
* Empty strings become `_`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { sanitizeCSharpIdentifier } from "@alloy-js/csharp";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
function sanitizeCSharpIdentifier(name: string): string;
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Parameters
|
|
17
|
+
|
|
18
|
+
| | | |
|
|
19
|
+
| ---- | ------ | - |
|
|
20
|
+
| name | string | |
|
|
21
|
+
|
|
22
|
+
## Returns
|
|
23
|
+
|
|
24
|
+
string A string that satisfies C# identifier character rules.
|
package/docs/api/index.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @alloy-js/csharp API Reference
|
|
2
2
|
|
|
3
3
|
- [components](components/index.md) (53 items)
|
|
4
|
-
- [
|
|
4
|
+
- [contexts](contexts/index.md) (1 items)
|
|
5
|
+
- [functions](functions/index.md) (35 items)
|
|
5
6
|
- [types](types/index.md) (60 items)
|
|
6
|
-
- [variables](variables/index.md) (
|
|
7
|
+
- [variables](variables/index.md) (2 items)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# csharpKeywords
|
|
2
|
+
|
|
3
|
+
C# reserved keywords that cannot be used as identifiers without `@` prefix. These are case-sensitive in C#.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
csharpKeywords: ReadonlySet<string>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## See also
|
|
10
|
+
|
|
11
|
+
* <https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alloy-js/csharp",
|
|
3
|
-
"version": "0.23.0-dev.
|
|
3
|
+
"version": "0.23.0-dev.11",
|
|
4
4
|
"description": "Alloy components for CSharp language.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"author": "jhendrix@microsoft.com",
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.
|
|
46
|
+
"@alloy-js/core": "~0.22.0 || >= 0.23.0-dev.18",
|
|
47
47
|
"@alloy-js/msbuild": "~0.22.0 || >= 0.23.0-dev.2",
|
|
48
48
|
"change-case": "^5.4.4",
|
|
49
49
|
"marked": "^16.1.1",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@alloy-js/cli": "~0.22.0 || >= 0.23.0-dev.6",
|
|
54
54
|
"@alloy-js/rollup-plugin": "~0.1.0 || >= 0.1.1-dev.2",
|
|
55
|
-
"@alloy-js/typescript": "~0.22.0 || >= 0.23.0-dev.
|
|
55
|
+
"@alloy-js/typescript": "~0.22.0 || >= 0.23.0-dev.10",
|
|
56
56
|
"@microsoft/api-extractor": "~7.52.8",
|
|
57
57
|
"@rollup/plugin-typescript": "^12.1.2",
|
|
58
58
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -207,3 +207,39 @@ describe("format", () => {
|
|
|
207
207
|
`);
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
|
+
|
|
211
|
+
describe("name policy deduplication", () => {
|
|
212
|
+
it("deduplicates parameters that collide after case conversion", () => {
|
|
213
|
+
const params = [
|
|
214
|
+
{ name: "my-param", type: "int" },
|
|
215
|
+
{ name: "myParam", type: "string" },
|
|
216
|
+
];
|
|
217
|
+
expect(
|
|
218
|
+
<Wrapper>
|
|
219
|
+
<Method public name="MethodOne" parameters={params} />
|
|
220
|
+
</Wrapper>,
|
|
221
|
+
).toRenderTo(`
|
|
222
|
+
public class TestClass
|
|
223
|
+
{
|
|
224
|
+
public void MethodOne(int myParam, string myParam_2) {}
|
|
225
|
+
}
|
|
226
|
+
`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("deduplicates parameters that are keywords", () => {
|
|
230
|
+
const params = [
|
|
231
|
+
{ name: "string", type: "int" },
|
|
232
|
+
{ name: "string", type: "bool" },
|
|
233
|
+
];
|
|
234
|
+
expect(
|
|
235
|
+
<Wrapper>
|
|
236
|
+
<Method public name="MethodOne" parameters={params} />
|
|
237
|
+
</Wrapper>,
|
|
238
|
+
).toRenderTo(`
|
|
239
|
+
public class TestClass
|
|
240
|
+
{
|
|
241
|
+
public void MethodOne(int @string, bool string_2) {}
|
|
242
|
+
}
|
|
243
|
+
`);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether the provided name is a valid C# identifier (without `@` prefix).
|
|
3
|
+
* Does not account for keyword conflicts — use {@link isCSharpKeyword} for that.
|
|
4
|
+
*
|
|
5
|
+
* @param name - The name to validate.
|
|
6
|
+
* @returns true if the name matches C# identifier rules (letter or underscore start, word chars after).
|
|
7
|
+
*/
|
|
8
|
+
export function isValidCSharpIdentifier(name: string): boolean {
|
|
9
|
+
return /^[A-Za-z_]\w*$/.test(name);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Transforms an arbitrary string into a valid C# identifier by replacing
|
|
14
|
+
* invalid characters. The result may still be a C# keyword — callers
|
|
15
|
+
* should combine with keyword escaping if needed.
|
|
16
|
+
*
|
|
17
|
+
* - If the first character is not a letter or underscore, a `_` prefix is added.
|
|
18
|
+
* - Subsequent non-word characters are replaced with `_`.
|
|
19
|
+
* - Empty strings become `_`.
|
|
20
|
+
*
|
|
21
|
+
* @param name - The string to sanitize.
|
|
22
|
+
* @returns A string that satisfies C# identifier character rules.
|
|
23
|
+
*/
|
|
24
|
+
export function sanitizeCSharpIdentifier(name: string): string {
|
|
25
|
+
if (name.length === 0) return "_";
|
|
26
|
+
|
|
27
|
+
const chars: string[] = [];
|
|
28
|
+
for (let i = 0; i < name.length; i++) {
|
|
29
|
+
const ch = name[i];
|
|
30
|
+
if (i === 0) {
|
|
31
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
32
|
+
chars.push(ch);
|
|
33
|
+
} else {
|
|
34
|
+
chars.push("_");
|
|
35
|
+
// Keep the original char if it's a word char (e.g., digit)
|
|
36
|
+
if (/\w/.test(ch)) {
|
|
37
|
+
chars.push(ch);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
chars.push(/\w/.test(ch) ? ch : "_");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return chars.join("");
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./access.jsx";
|
|
|
2
2
|
export * from "./components/index.js";
|
|
3
3
|
export * from "./contexts/format-options.js";
|
|
4
4
|
export * from "./create-library.js";
|
|
5
|
+
export * from "./identifier-utils.js";
|
|
6
|
+
export * from "./keywords.js";
|
|
5
7
|
export * from "./modifiers.js";
|
|
6
8
|
export * from "./name-policy.js";
|
|
7
9
|
export * from "./scopes/index.js";
|
package/src/keywords.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C# reserved keywords that cannot be used as identifiers without `@` prefix.
|
|
3
|
+
* These are case-sensitive in C#.
|
|
4
|
+
*
|
|
5
|
+
* @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/
|
|
6
|
+
*/
|
|
7
|
+
export const csharpKeywords: ReadonlySet<string> = new Set([
|
|
8
|
+
"abstract",
|
|
9
|
+
"as",
|
|
10
|
+
"base",
|
|
11
|
+
"bool",
|
|
12
|
+
"break",
|
|
13
|
+
"byte",
|
|
14
|
+
"case",
|
|
15
|
+
"catch",
|
|
16
|
+
"char",
|
|
17
|
+
"checked",
|
|
18
|
+
"class",
|
|
19
|
+
"const",
|
|
20
|
+
"continue",
|
|
21
|
+
"decimal",
|
|
22
|
+
"default",
|
|
23
|
+
"delegate",
|
|
24
|
+
"do",
|
|
25
|
+
"double",
|
|
26
|
+
"else",
|
|
27
|
+
"enum",
|
|
28
|
+
"event",
|
|
29
|
+
"explicit",
|
|
30
|
+
"extern",
|
|
31
|
+
"false",
|
|
32
|
+
"finally",
|
|
33
|
+
"fixed",
|
|
34
|
+
"float",
|
|
35
|
+
"for",
|
|
36
|
+
"foreach",
|
|
37
|
+
"goto",
|
|
38
|
+
"if",
|
|
39
|
+
"implicit",
|
|
40
|
+
"in",
|
|
41
|
+
"int",
|
|
42
|
+
"interface",
|
|
43
|
+
"internal",
|
|
44
|
+
"is",
|
|
45
|
+
"lock",
|
|
46
|
+
"long",
|
|
47
|
+
"namespace",
|
|
48
|
+
"new",
|
|
49
|
+
"null",
|
|
50
|
+
"object",
|
|
51
|
+
"operator",
|
|
52
|
+
"out",
|
|
53
|
+
"override",
|
|
54
|
+
"params",
|
|
55
|
+
"private",
|
|
56
|
+
"protected",
|
|
57
|
+
"public",
|
|
58
|
+
"readonly",
|
|
59
|
+
"ref",
|
|
60
|
+
"return",
|
|
61
|
+
"sbyte",
|
|
62
|
+
"sealed",
|
|
63
|
+
"short",
|
|
64
|
+
"sizeof",
|
|
65
|
+
"stackalloc",
|
|
66
|
+
"static",
|
|
67
|
+
"string",
|
|
68
|
+
"struct",
|
|
69
|
+
"switch",
|
|
70
|
+
"this",
|
|
71
|
+
"throw",
|
|
72
|
+
"true",
|
|
73
|
+
"try",
|
|
74
|
+
"typeof",
|
|
75
|
+
"uint",
|
|
76
|
+
"ulong",
|
|
77
|
+
"unchecked",
|
|
78
|
+
"unsafe",
|
|
79
|
+
"ushort",
|
|
80
|
+
"using",
|
|
81
|
+
"virtual",
|
|
82
|
+
"void",
|
|
83
|
+
"volatile",
|
|
84
|
+
"while",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* C# contextual keywords that are reserved in certain contexts.
|
|
89
|
+
* While not always reserved, treating them as keywords in generated code
|
|
90
|
+
* avoids subtle context-dependent issues.
|
|
91
|
+
*
|
|
92
|
+
* @see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/#contextual-keywords
|
|
93
|
+
*/
|
|
94
|
+
export const csharpContextualKeywords: ReadonlySet<string> = new Set([
|
|
95
|
+
"add",
|
|
96
|
+
"allows",
|
|
97
|
+
"alias",
|
|
98
|
+
"and",
|
|
99
|
+
"ascending",
|
|
100
|
+
"args",
|
|
101
|
+
"async",
|
|
102
|
+
"await",
|
|
103
|
+
"by",
|
|
104
|
+
"descending",
|
|
105
|
+
"dynamic",
|
|
106
|
+
"equals",
|
|
107
|
+
"field",
|
|
108
|
+
"file",
|
|
109
|
+
"from",
|
|
110
|
+
"get",
|
|
111
|
+
"global",
|
|
112
|
+
"group",
|
|
113
|
+
"init",
|
|
114
|
+
"into",
|
|
115
|
+
"join",
|
|
116
|
+
"let",
|
|
117
|
+
"managed",
|
|
118
|
+
"nameof",
|
|
119
|
+
"nint",
|
|
120
|
+
"not",
|
|
121
|
+
"notnull",
|
|
122
|
+
"nuint",
|
|
123
|
+
"on",
|
|
124
|
+
"or",
|
|
125
|
+
"orderby",
|
|
126
|
+
"partial",
|
|
127
|
+
"record",
|
|
128
|
+
"remove",
|
|
129
|
+
"required",
|
|
130
|
+
"scoped",
|
|
131
|
+
"select",
|
|
132
|
+
"set",
|
|
133
|
+
"unmanaged",
|
|
134
|
+
"value",
|
|
135
|
+
"var",
|
|
136
|
+
"when",
|
|
137
|
+
"where",
|
|
138
|
+
"with",
|
|
139
|
+
"yield",
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns true if the given name is a C# reserved keyword.
|
|
144
|
+
* The check is case-sensitive, matching C# language semantics.
|
|
145
|
+
*
|
|
146
|
+
* Note: this only checks reserved keywords, not contextual keywords.
|
|
147
|
+
* Contextual keywords are only reserved in specific language contexts
|
|
148
|
+
* and are valid identifiers elsewhere (e.g., `value` is valid as a parameter name).
|
|
149
|
+
* Use {@link isCSharpContextualKeyword} to check contextual keywords separately.
|
|
150
|
+
*/
|
|
151
|
+
export function isCSharpKeyword(name: string): boolean {
|
|
152
|
+
return csharpKeywords.has(name);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Returns true if the given name is a C# contextual keyword.
|
|
157
|
+
* Contextual keywords are only reserved in specific language contexts
|
|
158
|
+
* and are generally valid as identifiers.
|
|
159
|
+
*/
|
|
160
|
+
export function isCSharpContextualKeyword(name: string): boolean {
|
|
161
|
+
return csharpContextualKeywords.has(name);
|
|
162
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
isValidCSharpIdentifier,
|
|
4
|
+
sanitizeCSharpIdentifier,
|
|
5
|
+
} from "./identifier-utils.js";
|
|
6
|
+
import {
|
|
7
|
+
csharpContextualKeywords,
|
|
8
|
+
csharpKeywords,
|
|
9
|
+
isCSharpContextualKeyword,
|
|
10
|
+
isCSharpKeyword,
|
|
11
|
+
} from "./keywords.js";
|
|
12
|
+
import { createCSharpNamePolicy } from "./name-policy.js";
|
|
13
|
+
|
|
14
|
+
describe("isCSharpKeyword", () => {
|
|
15
|
+
it("recognizes reserved keywords", () => {
|
|
16
|
+
expect(isCSharpKeyword("class")).toBe(true);
|
|
17
|
+
expect(isCSharpKeyword("interface")).toBe(true);
|
|
18
|
+
expect(isCSharpKeyword("string")).toBe(true);
|
|
19
|
+
expect(isCSharpKeyword("int")).toBe(true);
|
|
20
|
+
expect(isCSharpKeyword("void")).toBe(true);
|
|
21
|
+
expect(isCSharpKeyword("return")).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("does not match contextual keywords", () => {
|
|
25
|
+
expect(isCSharpKeyword("async")).toBe(false);
|
|
26
|
+
expect(isCSharpKeyword("value")).toBe(false);
|
|
27
|
+
expect(isCSharpKeyword("var")).toBe(false);
|
|
28
|
+
expect(isCSharpKeyword("record")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("is case-sensitive — PascalCase versions are not keywords", () => {
|
|
32
|
+
expect(isCSharpKeyword("Class")).toBe(false);
|
|
33
|
+
expect(isCSharpKeyword("String")).toBe(false);
|
|
34
|
+
expect(isCSharpKeyword("Int")).toBe(false);
|
|
35
|
+
expect(isCSharpKeyword("Void")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("rejects non-keywords", () => {
|
|
39
|
+
expect(isCSharpKeyword("MyClass")).toBe(false);
|
|
40
|
+
expect(isCSharpKeyword("foo")).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("keyword sets are non-empty", () => {
|
|
44
|
+
expect(csharpKeywords.size).toBeGreaterThan(70);
|
|
45
|
+
expect(csharpContextualKeywords.size).toBeGreaterThan(30);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("isCSharpContextualKeyword", () => {
|
|
50
|
+
it("recognizes contextual keywords", () => {
|
|
51
|
+
expect(isCSharpContextualKeyword("async")).toBe(true);
|
|
52
|
+
expect(isCSharpContextualKeyword("await")).toBe(true);
|
|
53
|
+
expect(isCSharpContextualKeyword("value")).toBe(true);
|
|
54
|
+
expect(isCSharpContextualKeyword("var")).toBe(true);
|
|
55
|
+
expect(isCSharpContextualKeyword("record")).toBe(true);
|
|
56
|
+
expect(isCSharpContextualKeyword("required")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("does not match reserved keywords", () => {
|
|
60
|
+
expect(isCSharpContextualKeyword("class")).toBe(false);
|
|
61
|
+
expect(isCSharpContextualKeyword("int")).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("isValidCSharpIdentifier", () => {
|
|
66
|
+
it("accepts valid identifiers", () => {
|
|
67
|
+
expect(isValidCSharpIdentifier("MyClass")).toBe(true);
|
|
68
|
+
expect(isValidCSharpIdentifier("_private")).toBe(true);
|
|
69
|
+
expect(isValidCSharpIdentifier("name123")).toBe(true);
|
|
70
|
+
expect(isValidCSharpIdentifier("a")).toBe(true);
|
|
71
|
+
expect(isValidCSharpIdentifier("_")).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("rejects invalid identifiers", () => {
|
|
75
|
+
expect(isValidCSharpIdentifier("123start")).toBe(false);
|
|
76
|
+
expect(isValidCSharpIdentifier("has-dash")).toBe(false);
|
|
77
|
+
expect(isValidCSharpIdentifier("has space")).toBe(false);
|
|
78
|
+
expect(isValidCSharpIdentifier("")).toBe(false);
|
|
79
|
+
expect(isValidCSharpIdentifier("foo.bar")).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("does not check for keywords (only character rules)", () => {
|
|
83
|
+
// "class" has valid characters, even though it's a keyword
|
|
84
|
+
expect(isValidCSharpIdentifier("class")).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("sanitizeCSharpIdentifier", () => {
|
|
89
|
+
it("passes through valid identifiers unchanged", () => {
|
|
90
|
+
expect(sanitizeCSharpIdentifier("ValidName")).toBe("ValidName");
|
|
91
|
+
expect(sanitizeCSharpIdentifier("_private")).toBe("_private");
|
|
92
|
+
expect(sanitizeCSharpIdentifier("name123")).toBe("name123");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("prefixes underscore when first char is a digit", () => {
|
|
96
|
+
expect(sanitizeCSharpIdentifier("1foo")).toBe("_1foo");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("replaces non-word characters with underscore", () => {
|
|
100
|
+
expect(sanitizeCSharpIdentifier("foo-bar")).toBe("foo_bar");
|
|
101
|
+
expect(sanitizeCSharpIdentifier("has space")).toBe("has_space");
|
|
102
|
+
expect(sanitizeCSharpIdentifier("a.b.c")).toBe("a_b_c");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("handles leading special characters", () => {
|
|
106
|
+
expect(sanitizeCSharpIdentifier("$foo")).toBe("_foo");
|
|
107
|
+
expect(sanitizeCSharpIdentifier("-bar")).toBe("_bar");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("handles empty string", () => {
|
|
111
|
+
expect(sanitizeCSharpIdentifier("")).toBe("_");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("createCSharpNamePolicy keyword escaping", () => {
|
|
116
|
+
const policy = createCSharpNamePolicy();
|
|
117
|
+
|
|
118
|
+
describe("PascalCase elements avoid most keyword conflicts naturally", () => {
|
|
119
|
+
it("class element: 'string' becomes 'String' (not a keyword, no escape)", () => {
|
|
120
|
+
expect(policy.getName("string", "class")).toBe("String");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("class element: 'class' becomes 'Class' (not a keyword, no escape)", () => {
|
|
124
|
+
expect(policy.getName("class", "class")).toBe("Class");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("class-property element: 'value' becomes 'Value' (not a keyword, no escape)", () => {
|
|
128
|
+
expect(policy.getName("value", "class-property")).toBe("Value");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("camelCase elements may hit keywords and get @-escaped", () => {
|
|
133
|
+
it("parameter: 'string' stays 'string' → gets @-escaped", () => {
|
|
134
|
+
expect(policy.getName("string", "parameter")).toBe("@string");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("variable: 'int' stays 'int' → gets @-escaped", () => {
|
|
138
|
+
expect(policy.getName("int", "variable")).toBe("@int");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("parameter: 'return' stays 'return' → gets @-escaped", () => {
|
|
142
|
+
expect(policy.getName("return", "parameter")).toBe("@return");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("variable: 'value' stays 'value' — contextual keyword, not escaped", () => {
|
|
146
|
+
expect(policy.getName("value", "variable")).toBe("value");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("parameter: 'async' stays 'async' — contextual keyword, not escaped", () => {
|
|
150
|
+
expect(policy.getName("async", "parameter")).toBe("async");
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("non-keyword names pass through normally", () => {
|
|
155
|
+
it("parameter: 'myParam' stays 'myParam'", () => {
|
|
156
|
+
expect(policy.getName("myParam", "parameter")).toBe("myParam");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("class: 'my-model' becomes 'MyModel'", () => {
|
|
160
|
+
expect(policy.getName("my-model", "class")).toBe("MyModel");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("parameter: 'some-param' becomes 'someParam'", () => {
|
|
164
|
+
expect(policy.getName("some-param", "parameter")).toBe("someParam");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("namespace handling", () => {
|
|
169
|
+
it("applies PascalCase", () => {
|
|
170
|
+
expect(policy.getName("my-service", "namespace")).toBe("MyService");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("single segment works", () => {
|
|
174
|
+
expect(policy.getName("system", "namespace")).toBe("System");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("escapes keyword segments", () => {
|
|
178
|
+
// "namespace" as a namespace segment → PascalCase → "Namespace" → not a keyword
|
|
179
|
+
expect(policy.getName("namespace", "namespace")).toBe("Namespace");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("constant and private member escaping", () => {
|
|
184
|
+
it("constant: 'class' becomes 'CLASS' (not a keyword)", () => {
|
|
185
|
+
expect(policy.getName("class", "constant")).toBe("CLASS");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("class-member-private: 'value' becomes '_value' (not a keyword)", () => {
|
|
189
|
+
expect(policy.getName("value", "class-member-private")).toBe("_value");
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("identifier sanitization", () => {
|
|
194
|
+
it("sanitizes names starting with digits", () => {
|
|
195
|
+
expect(policy.getName("123foo", "class")).toBe("_123foo");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("sanitizes names starting with digits for camelCase elements", () => {
|
|
199
|
+
expect(policy.getName("123param", "parameter")).toBe("_123param");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("sanitizes empty string", () => {
|
|
203
|
+
expect(policy.getName("", "class")).toBe("_");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("sanitizes namespace starting with digits", () => {
|
|
207
|
+
expect(policy.getName("123service", "namespace")).toBe("_123service");
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|