Repository: stoplightio/spectral (ts)

Issue:
Camel case function does not accommodate uppercase single letter words at the end of a property name
**Describe the bug**

When using the built-in function `casing` with the argument `camel` Spectral raises an error when it encounters a single, uppercase word at the end of the camel-cased string. For example:

* coordinateX
* scaleY

**To Reproduce**

Using the following OpenAPI document:

```yaml
openapi: 3.0.3
info:
  title: Spectral evidence
  description: Noddy API to use in Spectral ticket
  version: 0.0.1
paths:
  /:
    get:
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  coordinateX:
                    type: string
                  scaleY:
                    type: string
```

And the following ruleset:

```yaml
extends: spectral:oas
rules:
  operation-operationId: off
  oas3-api-servers: off
  info-contact: off
  operation-description: off
  operation-tags: off
  camel-case-enforcement:
    message: "Property is not named in lower camel case: {{path}}, {{property}}"
    severity: error
    given: $..[?(@.type === 'object' && @.properties)].properties.*~
    then:
      function: casing
      functionOptions:
        type: camel
```

Executing Spectral gives the following output:

```bash
➜  spectral-evidence spectral lint --ruleset ./evidence.yaml openapi.yaml
OpenAPI 3.x detected

/Users/chris/Documents/git/github/spectral-evidence/openapi.yaml
 22:31  error  camel-case-enforcement  Property is not named in lower camel case: #/paths/~1/get/responses/200/content/application~1json/schema/properties/coordinateX, coordinateX  paths./.get.responses[200].content.application/json.schema.properties.coordinateX
 24:26  error  camel-case-enforcement  Property is not named in lower camel case: #/paths/~1/get/responses/200/content/application~1json/schema/properties/scaleY, scaleY            paths./.get.responses[200].content.application/json.schema.properties.scaleY

✖ 2 problems (2 errors, 0 warnings, 0 infos, 0 hints)

➜  spectral-evidence
```

I think this is the root of the issue: [here](https://github.com/stoplightio/spectral/blob/develop/src/functions/casing.ts#L26) - the regex should allow end of line I guess i.e. `([a-z{__DIGITS__}]+|$)`

**Expected behavior**

The ruleset should not raise the camel-case-enforcement errors as semantically the property names are correct.

**Environment:**
 - Node version: 12.20.0
 - Library version: 5.7.2
 - OS: macOs Catalina 10.15.7


Pull request description:
fix(casing): {pascal,camel}-case does not support single uppercase letters at the end
Fixes #1500.

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



Patch:
diff --git a/src/functions/casing.ts b/src/functions/casing.ts
index fb85400e..ff18d22f 100644
--- a/src/functions/casing.ts
+++ b/src/functions/casing.ts
@@ -23,8 +23,8 @@ export interface ICasingOptions {
 
 const CASES: Dictionary<string, CasingType> = {
   [CasingType.flat]: '[a-z][a-z{__DIGITS__}]*',
-  [CasingType.camel]: '[a-z][a-z{__DIGITS__}]*(?:[A-Z{__DIGITS__}][a-z{__DIGITS__}]+)*',
-  [CasingType.pascal]: '[A-Z][a-z{__DIGITS__}]*(?:[A-Z{__DIGITS__}][a-z{__DIGITS__}]+)*',
+  [CasingType.camel]: '[a-z][a-z{__DIGITS__}]*(?:[A-Z{__DIGITS__}](?:[a-z{__DIGITS__}]+|$))*',
+  [CasingType.pascal]: '[A-Z][a-z{__DIGITS__}]*(?:[A-Z{__DIGITS__}](?:[a-z{__DIGITS__}]+|$))*',
   [CasingType.kebab]: '[a-z][a-z{__DIGITS__}]*(?:-[a-z{__DIGITS__}]+)*',
   [CasingType.cobol]: '[A-Z][A-Z{__DIGITS__}]*(?:-[A-Z{__DIGITS__}]+)*',
   [CasingType.snake]: '[a-z][a-z{__DIGITS__}]*(?:_[a-z{__DIGITS__}]+)*',


Test patch:
diff --git a/src/functions/__tests__/casing.test.ts b/src/functions/__tests__/casing.test.ts
index 8da9fa88..148b6a19 100644
--- a/src/functions/__tests__/casing.test.ts
+++ b/src/functions/__tests__/casing.test.ts
@@ -46,7 +46,7 @@ describe('casing', () => {
 
     describe('camel', () => {
       const invalid = ['foo_test', 'Foo', '1fooBarBaz', '123', 'foo-bar'];
-      const valid = ['foo', 'fooBar', 'fooBarBaz'];
+      const valid = ['foo', 'fooBar', 'fooBarBaz', 'coordinateX'];
       const validWithDigits = ['foo1', 'foo24Bar', 'fooBar0Baz323'];
 
       test.each(invalid)('should recognize invalid target %s', target => {
@@ -70,7 +70,7 @@ describe('casing', () => {
 
     describe('pascal', () => {
       const invalid = ['foo_test', '123', '1fooBarBaz', 'fooBarBaz1', 'fooBar', 'foo1', 'foo-bar'];
-      const valid = ['Foo', 'FooBar', 'FooBarBaz'];
+      const valid = ['Foo', 'FooBar', 'FooBarBaz', 'CoordinateZ'];
       const validWithDigits = ['Foo1', 'FooBarBaz1'];
 
       test.each(invalid)('should recognize invalid target %s', target => {