Repository: stoplightio/spectral (ts)

Issue:
Incorrect line number when semicolons are present in a key
**Describe the bug**

The displayed line number for errors are always set to the line previous to one containing a semicolon in the key.

**To Reproduce**

Ruleset

```
rules:
  therule:
    description: schema type is array
    given: "$.paths[*].[*].responses[*].content[*].schema.type"
    then:
      function: pattern
      functionOptions:
        match: "array"
```

Document:

```
openapi: 3.0.3
paths:
  /customer:
    get:
      responses:
        "200":
          content: # recieves error on this line
            application/json;charset=utf-8: # semicolon causes the problem
              schema:
                type: string # expecting error on this line
```

**Expected behavior**

Error should be on line 10 (type).

**Actual behavior**

Error shows up on line 7 (content), right before the key including a semicolon.

**Environment (remove any that are not applicable):**

- Library version: 5.9.1

**Additional comments**

We have tried enclosing the key in double quotations which gave no difference in result.

Pull request:
fix(core): incorrect range when semicolons are present in a key
Fixes #1697.

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No



Patch:
diff --git a/packages/core/src/runner/runner.ts b/packages/core/src/runner/runner.ts
index c1c82e43..49c4bc41 100644
--- a/packages/core/src/runner/runner.ts
+++ b/packages/core/src/runner/runner.ts
@@ -11,6 +11,7 @@ import { RunnerRuntime } from './runtime';
 import { IRunnerInternalContext } from './types';
 import { Rule } from '../ruleset/rule/rule';
 import { Ruleset } from '../ruleset/ruleset';
+import { toPath } from 'lodash';
 
 const runRule = (context: IRunnerInternalContext, rule: Rule): void => {
   const target = rule.resolved ? context.documentInventory.resolved : context.documentInventory.unresolved;
@@ -36,7 +37,7 @@ const runRule = (context: IRunnerInternalContext, rule: Rule): void => {
             context,
             {
               // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
-              path: JSONPath.toPathArray(result.path),
+              path: toPath(result.path.slice(1)),
               // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
               value: result.value,
             },
diff --git a/packages/core/src/runner/utils/getLintTargets.ts b/packages/core/src/runner/utils/getLintTargets.ts
index 2ce21163..33592599 100644
--- a/packages/core/src/runner/utils/getLintTargets.ts
+++ b/packages/core/src/runner/utils/getLintTargets.ts
@@ -25,8 +25,8 @@ export const getLintTargets = (targetValue: unknown, field: Optional<string>): I
         resultType: 'all',
         callback(result) {
           targets.push({
-            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-            path: JSONPath.toPathArray(result.path).slice(1),
+            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
+            path: toPath(result.path.slice(1)),
             // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
             value: result.value,
           });


Test patch:
diff --git a/packages/core/src/__tests__/linter.test.ts b/packages/core/src/__tests__/linter.test.ts
index 16b422b5..cedba997 100644
--- a/packages/core/src/__tests__/linter.test.ts
+++ b/packages/core/src/__tests__/linter.test.ts
@@ -195,6 +195,52 @@ describe('linter', () => {
     ]);
   });
 
+  test('should handle semicolons in property keys', async () => {
+    const document = new Document(
+      `paths:
+  content:
+    application/json;charset=utf-8: # semicolon causes the problem
+      schema:
+        type: string # expecting error on this line`,
+      Parsers.Yaml,
+    );
+
+    const spectral = new Spectral();
+
+    spectral.setRuleset({
+      rules: {
+        rule: {
+          given: '$..type',
+          then: {
+            function: pattern,
+            functionOptions: {
+              match: 'array',
+            },
+          },
+        },
+      },
+    });
+
+    expect(await spectral.run(document)).toEqual([
+      {
+        code: 'rule',
+        message: '"string" must match the pattern "array"',
+        path: ['paths', 'content', 'application/json;charset=utf-8', 'schema', 'type'],
+        range: {
+          end: {
+            character: 20,
+            line: 4,
+          },
+          start: {
+            character: 14,
+            line: 4,
+          },
+        },
+        severity: DiagnosticSeverity.Warning,
+      },
+    ]);
+  });
+
   test('should support human readable severity levels', async () => {
     spectral.setRuleset({
       rules: {