@reqlan/language 1.9.6 → 1.10.1

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.
Files changed (87) hide show
  1. package/README.md +42 -0
  2. package/README.template.md +2 -0
  3. package/out/file-path-rewrite.d.ts +1 -1
  4. package/out/generated/ast.d.ts +2 -2
  5. package/out/generated/ast.js +1 -1
  6. package/out/generated/ast.js.map +1 -1
  7. package/out/generated/grammar.js +1 -1
  8. package/out/generated/grammar.js.map +1 -1
  9. package/out/index.d.ts +3 -1
  10. package/out/index.js +3 -1
  11. package/out/index.js.map +1 -1
  12. package/out/reqlan-code-action-provider.d.ts +1 -0
  13. package/out/reqlan-code-action-provider.js +12 -21
  14. package/out/reqlan-code-action-provider.js.map +1 -1
  15. package/out/reqlan-comment-diagnostics.d.ts +11 -0
  16. package/out/reqlan-comment-diagnostics.js +81 -11
  17. package/out/reqlan-comment-diagnostics.js.map +1 -1
  18. package/out/reqlan-comment-resolver.d.ts +1 -1
  19. package/out/reqlan-completion-provider.d.ts +2 -2
  20. package/out/reqlan-definition-provider.js +1 -1
  21. package/out/reqlan-definition-provider.js.map +1 -1
  22. package/out/reqlan-document-builder.d.ts +9 -0
  23. package/out/reqlan-document-builder.js +26 -0
  24. package/out/reqlan-document-builder.js.map +1 -0
  25. package/out/reqlan-document-link-provider.d.ts +5 -1
  26. package/out/reqlan-document-link-provider.js +9 -1
  27. package/out/reqlan-document-link-provider.js.map +1 -1
  28. package/out/reqlan-embedded-file-references.d.ts +3 -0
  29. package/out/reqlan-embedded-file-references.js +47 -5
  30. package/out/reqlan-embedded-file-references.js.map +1 -1
  31. package/out/reqlan-file-link-resolver.d.ts +8 -0
  32. package/out/reqlan-file-link-resolver.js +46 -11
  33. package/out/reqlan-file-link-resolver.js.map +1 -1
  34. package/out/reqlan-file-references.d.ts +1 -0
  35. package/out/reqlan-file-references.js +24 -16
  36. package/out/reqlan-file-references.js.map +1 -1
  37. package/out/reqlan-idea-move-imports.d.ts +24 -0
  38. package/out/reqlan-idea-move-imports.js +309 -0
  39. package/out/reqlan-idea-move-imports.js.map +1 -0
  40. package/out/reqlan-idea-refactor.d.ts +12 -4
  41. package/out/reqlan-idea-refactor.js +85 -13
  42. package/out/reqlan-idea-refactor.js.map +1 -1
  43. package/out/reqlan-ignore-error.d.ts +2 -1
  44. package/out/reqlan-ignore-error.js +4 -17
  45. package/out/reqlan-ignore-error.js.map +1 -1
  46. package/out/reqlan-import-edits.d.ts +1 -0
  47. package/out/reqlan-import-edits.js +8 -0
  48. package/out/reqlan-import-edits.js.map +1 -1
  49. package/out/reqlan-imports.d.ts +3 -1
  50. package/out/reqlan-imports.js +2 -5
  51. package/out/reqlan-imports.js.map +1 -1
  52. package/out/reqlan-module.js +4 -1
  53. package/out/reqlan-module.js.map +1 -1
  54. package/out/reqlan-reference-at-position.d.ts +3 -0
  55. package/out/reqlan-reference-at-position.js +8 -2
  56. package/out/reqlan-reference-at-position.js.map +1 -1
  57. package/out/reqlan-scope.d.ts +14 -0
  58. package/out/reqlan-scope.js +58 -15
  59. package/out/reqlan-scope.js.map +1 -1
  60. package/out/reqlan-validator.d.ts +12 -1
  61. package/out/reqlan-validator.js +38 -4
  62. package/out/reqlan-validator.js.map +1 -1
  63. package/package.json +3 -2
  64. package/src/file-path-rewrite.ts +1 -1
  65. package/src/generated/ast.ts +2 -2
  66. package/src/generated/grammar.ts +1 -1
  67. package/src/index.ts +9 -0
  68. package/src/reqlan-code-action-provider.ts +12 -24
  69. package/src/reqlan-comment-diagnostics.ts +108 -11
  70. package/src/reqlan-comment-resolver.ts +1 -1
  71. package/src/reqlan-completion-provider.ts +2 -2
  72. package/src/reqlan-definition-provider.ts +1 -1
  73. package/src/reqlan-document-builder.ts +35 -0
  74. package/src/reqlan-document-link-provider.ts +23 -3
  75. package/src/reqlan-embedded-file-references.ts +54 -5
  76. package/src/reqlan-file-link-resolver.ts +56 -11
  77. package/src/reqlan-file-references.ts +26 -16
  78. package/src/reqlan-idea-move-imports.ts +398 -0
  79. package/src/reqlan-idea-refactor.ts +116 -14
  80. package/src/reqlan-ignore-error.ts +4 -18
  81. package/src/reqlan-import-edits.ts +9 -0
  82. package/src/reqlan-imports.ts +4 -5
  83. package/src/reqlan-module.ts +4 -1
  84. package/src/reqlan-reference-at-position.ts +16 -2
  85. package/src/reqlan-scope.ts +73 -16
  86. package/src/reqlan-validator.ts +48 -4
  87. package/src/reqlan.langium +4 -1
@@ -2,8 +2,8 @@
2
2
  * Code completion for references, import paths, file paths, and attributes.
3
3
  * rq:["../../../reqlan rq/extension/configuration.rq".configuration_import_roots]
4
4
  * rq:["../../../reqlan rq/language/imports.rq".configuration_import_root_alias]
5
- * rq:["../../../reqlan rq/extension/features-syntax-highlighting.rq".reference_code_completion]
6
- * rq:["../../../reqlan rq/extension/features-syntax-highlighting.rq".reference_code_completion_sequencing]
5
+ * rq:["../../../reqlan rq/extension/syntax/features-syntax-highlighting.rq".reference_code_completion]
6
+ * rq:["../../../reqlan rq/extension/syntax/features-syntax-highlighting.rq".reference_code_completion_sequencing]
7
7
  * rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_code_completion]
8
8
  * rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_code_completion_ranking]
9
9
  * rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_code_completion_rendering]
@@ -278,7 +278,7 @@ export class ReqlanDefinitionProvider extends DefaultDefinitionProvider {
278
278
 
279
279
  private createFileLink(source: CstNode, reference: FileReference | FileSymbolReference): GoToLink | undefined {
280
280
  const resolved = resolveFileReferenceLink(reference, this.documents, this.fileSystem, this.pathContext());
281
- if (!resolved) {
281
+ if (!resolved || resolved.resolution === 'missing') {
282
282
  return undefined;
283
283
  }
284
284
  const targetDocument = this.documents.getDocument(URI.parse(resolved.targetUri));
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Rebuilds documents whose comment-reference diagnostics depend on other `.rq` files.
3
+ * rq:["../../../reqlan rq/extension/features-non-rq-code-comment/functional-code-comment-references.rq".comment_reference_resolution_error_state]
4
+ */
5
+ import {
6
+ DefaultDocumentBuilder,
7
+ type LangiumDocument,
8
+ type LangiumSharedCoreServices
9
+ } from 'langium';
10
+ import { shouldRelinkCommentReferences } from './reqlan-comment-diagnostics.js';
11
+ import { pathResolveContextFromServices } from './reqlan-path-resolve.js';
12
+
13
+ export class ReqlanDocumentBuilder extends DefaultDocumentBuilder {
14
+ constructor(services: LangiumSharedCoreServices) {
15
+ super(services);
16
+ }
17
+
18
+ protected override shouldRelink(document: LangiumDocument, changedUris: Set<string>): boolean {
19
+ if (super.shouldRelink(document, changedUris)) {
20
+ return true;
21
+ }
22
+ return shouldRelinkCommentReferences(
23
+ document,
24
+ changedUris,
25
+ pathResolveContextFromServices({
26
+ shared: {
27
+ workspace: {
28
+ WorkspaceManager: this.workspaceManager(),
29
+ FileSystemProvider: this.fileSystemProvider
30
+ }
31
+ }
32
+ })
33
+ );
34
+ }
35
+ }
@@ -1,11 +1,17 @@
1
1
  /**
2
- * Clickable document links for idea references, file references, and import paths.
2
+ * Clickable document links for idea references, file references, import paths, and comment references.
3
3
  * Same-file and cross-file idea refs both get links so the editor underline is consistent.
4
+ * Comment-reference links come from the same presentation as the missing-idea underline.
5
+ * Missing file refs get an error underline and no document link.
6
+ * rq:["../../../reqlan rq/extension/features-non-rq-code-comment/functional-code-comment-references.rq".comment_reference_resolution_error_state]
7
+ * rq:["../../../reqlan rq/extension/language-support/language-server-errors.rq".file_reference_errors]
4
8
  */
5
9
  import type { LangiumDocument } from 'langium';
10
+ import { URI } from 'langium';
6
11
  import type { DocumentLinkProvider } from 'langium/lsp';
7
12
  import type { DocumentLink, DocumentLinkParams } from 'vscode-languageserver';
8
13
  import { DocumentLink as LspDocumentLink } from 'vscode-languageserver';
14
+ import { presentCommentReferencesForDocument } from './reqlan-comment-diagnostics.js';
9
15
  import { collectFileLinks, resolvedFileLinkTargetUri } from './reqlan-file-link-resolver.js';
10
16
  import { folderReferenceCommandTarget } from './reqlan-reference-at-position.js';
11
17
  import { wildcardReferenceCommandTarget } from './reqlan-wildcard-resolve.js';
@@ -25,11 +31,12 @@ export class ReqlanDocumentLinkProvider implements DocumentLinkProvider {
25
31
  }
26
32
 
27
33
  getDocumentLinks(document: LangiumDocument, _params: DocumentLinkParams): DocumentLink[] {
28
- return collectFileLinks(
34
+ const pathContext = pathResolveContextFromServices(this.services);
35
+ const fileLinks = collectFileLinks(
29
36
  document,
30
37
  this.documents,
31
38
  this.fileSystem,
32
- pathResolveContextFromServices(this.services)
39
+ pathContext
33
40
  ).flatMap(link => {
34
41
  if (link.resolution === 'folder') {
35
42
  return [LspDocumentLink.create(
@@ -37,6 +44,9 @@ export class ReqlanDocumentLinkProvider implements DocumentLinkProvider {
37
44
  folderReferenceCommandTarget(link.targetUri)
38
45
  )];
39
46
  }
47
+ if (link.resolution === 'missing') {
48
+ return [];
49
+ }
40
50
  if (link.resolution === 'wildcard' && link.wildcardArgs) {
41
51
  return [LspDocumentLink.create(
42
52
  link.sourceRange,
@@ -49,5 +59,15 @@ export class ReqlanDocumentLinkProvider implements DocumentLinkProvider {
49
59
  }
50
60
  return [LspDocumentLink.create(link.sourceRange, target)];
51
61
  });
62
+ const commentLinks = presentCommentReferencesForDocument(
63
+ document,
64
+ this.documents,
65
+ this.fileSystem,
66
+ pathContext
67
+ ).links.map(link => LspDocumentLink.create(
68
+ link.range,
69
+ link.targetUri ?? URI.file(link.targetPath).toString()
70
+ ));
71
+ return [...fileLinks, ...commentLinks];
52
72
  }
53
73
  }
@@ -1,5 +1,8 @@
1
1
  /**
2
2
  * Locates quoted file paths embedded in .rq source text, including @tests list entries.
3
+ * Skips inline code (`…`) and fenced ``` blocks — those are opaque examples, not live refs.
4
+ * rq:["../../../reqlan rq/language/syntax.rq".inline_code]
5
+ * rq:["../../../reqlan rq/language/syntax.rq".code_snippets]
3
6
  */
4
7
  import type { LangiumDocument } from 'langium';
5
8
  import type { Position, Range } from 'vscode-languageserver';
@@ -18,27 +21,69 @@ const FILE_REFERENCE_LIKE = /(?:\.\w[\w.]*|\/)/;
18
21
  export function findEmbeddedFileReferencesInText(text: string, lineOffset = 0): EmbeddedFileReference[] {
19
22
  const references: EmbeddedFileReference[] = [];
20
23
  const lines = text.split(/\r?\n/);
24
+ let inFence = false;
21
25
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
22
26
  const line = lines[lineIndex];
27
+ if (isCodeFenceLine(line)) {
28
+ inFence = !inFence;
29
+ continue;
30
+ }
31
+ if (inFence) {
32
+ continue;
33
+ }
34
+ const opaque = inlineCodeSpans(line);
23
35
  for (const match of line.matchAll(BRACKETED_FILE_REFERENCE_PATTERN)) {
24
- pushEmbeddedReference(references, match, lineIndex, lineOffset, line);
36
+ pushEmbeddedReference(references, match, lineIndex, lineOffset, line, opaque);
25
37
  }
26
38
  for (const match of line.matchAll(QUOTED_FILE_REFERENCE_PATTERN)) {
27
39
  if (line.slice(Math.max(0, (match.index ?? 0) - 1), match.index).includes('[')) {
28
40
  continue;
29
41
  }
30
- pushEmbeddedReference(references, match, lineIndex, lineOffset, line);
42
+ pushEmbeddedReference(references, match, lineIndex, lineOffset, line, opaque);
31
43
  }
32
44
  }
33
45
  return references;
34
46
  }
35
47
 
48
+ function isCodeFenceLine(line: string): boolean {
49
+ return line.trimStart().startsWith('```');
50
+ }
51
+
52
+ function inlineCodeSpans(line: string): Array<{ start: number; end: number }> {
53
+ const spans: Array<{ start: number; end: number }> = [];
54
+ let index = 0;
55
+ while (index < line.length) {
56
+ if (line[index] !== '`') {
57
+ index++;
58
+ continue;
59
+ }
60
+ if (line.startsWith('```', index)) {
61
+ index += 3;
62
+ continue;
63
+ }
64
+ const close = line.indexOf('`', index + 1);
65
+ if (close < 0) {
66
+ break;
67
+ }
68
+ if (close > index + 1) {
69
+ spans.push({ start: index, end: close + 1 });
70
+ }
71
+ index = close + 1;
72
+ }
73
+ return spans;
74
+ }
75
+
76
+ function rangeOverlapsOpaque(start: number, end: number, opaque: Array<{ start: number; end: number }>): boolean {
77
+ return opaque.some(span => start < span.end && end > span.start);
78
+ }
79
+
36
80
  function pushEmbeddedReference(
37
81
  references: EmbeddedFileReference[],
38
82
  match: RegExpMatchArray,
39
83
  lineIndex: number,
40
84
  lineOffset: number,
41
- line: string
85
+ line: string,
86
+ opaque: Array<{ start: number; end: number }>
42
87
  ): void {
43
88
  const quoted = match[1];
44
89
  const file = parseReqlanQuotedString(quoted);
@@ -46,14 +91,18 @@ function pushEmbeddedReference(
46
91
  return;
47
92
  }
48
93
  const start = match.index ?? 0;
49
- if (isRangeInsideMarkdownLinkLabel(line, start, start + match[0].length)) {
94
+ const end = start + match[0].length;
95
+ if (rangeOverlapsOpaque(start, end, opaque)) {
96
+ return;
97
+ }
98
+ if (isRangeInsideMarkdownLinkLabel(line, start, end)) {
50
99
  return;
51
100
  }
52
101
  references.push({
53
102
  file,
54
103
  range: {
55
104
  start: { line: lineOffset + lineIndex, character: start },
56
- end: { line: lineOffset + lineIndex, character: start + match[0].length }
105
+ end: { line: lineOffset + lineIndex, character: end }
57
106
  }
58
107
  });
59
108
  }
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * Resolves file references, idea references, and import paths to target URIs and editor ranges.
3
3
  * Document links use these so same-file and cross-file refs share the same underline/click affordance.
4
+ * Missing file refs stay in this result so the validator can underline them without creating a link.
5
+ * rq:["../../../reqlan rq/extension/language-support/language-server-errors.rq".file_reference_errors]
6
+ * rq:["../../../reqlan rq/extension/syntax/features-syntax.rq".file_references]
7
+ * rq:["../../../reqlan rq/extension/language-support/features-imports.rq".import_folder_targets]
4
8
  */
5
9
  import type { AstNode, CstNode, FileSystemProvider, LangiumDocument, LangiumDocuments, Reference, URI } from 'langium';
6
10
  import { AstUtils, CstUtils, GrammarUtils } from 'langium';
@@ -50,6 +54,8 @@ export type ReferenceResolution = 'file' | 'folder' | 'missing' | 'wildcard';
50
54
 
51
55
  export type FileLinkTargetIssue = 'empty' | 'parse-error';
52
56
 
57
+ export const FILE_REFERENCE_MISSING = 'file-reference-missing';
58
+
53
59
  export interface ResolvedFileLink {
54
60
  sourceRange: Range;
55
61
  targetUri: string;
@@ -57,6 +63,8 @@ export interface ResolvedFileLink {
57
63
  resolution?: ReferenceResolution;
58
64
  folderFiles?: string[];
59
65
  targetIssue?: FileLinkTargetIssue;
66
+ /** Authored path (no test/line suffix) for missing-file diagnostics. */
67
+ authoredPath?: string;
60
68
  /** Present when resolution is `wildcard`. */
61
69
  wildcardArgs?: WildcardReferenceArgs;
62
70
  wildcardMatches?: WildcardMatch[];
@@ -126,14 +134,18 @@ export function resolveImportPathLink(
126
134
  }
127
135
  const imported = findImportedDocument(path, document, documents, context);
128
136
  const target = imported?.parseResult.value.$cstNode;
129
- if (!imported || !target) {
137
+ if (imported && target) {
138
+ return {
139
+ sourceRange,
140
+ targetUri: imported.textDocument.uri,
141
+ targetRange: target.range
142
+ };
143
+ }
144
+ const fileSystem = context?.fileSystem;
145
+ if (!fileSystem) {
130
146
  return undefined;
131
147
  }
132
- return {
133
- sourceRange,
134
- targetUri: imported.textDocument.uri,
135
- targetRange: target.range
136
- };
148
+ return resolveImportedFileLink(document, documents, fileSystem, path, sourceRange, context);
137
149
  }
138
150
 
139
151
  export function resolveQualifiedReferencePathLink(
@@ -238,21 +250,28 @@ function resolvePathStringLink(
238
250
  targetUri: URI,
239
251
  sourceRange: Range
240
252
  ): ResolvedFileLink | undefined {
253
+ if (!parsed.filePath.trim()) {
254
+ return missingFileLink(sourceRange, targetUri, parsed.filePath);
255
+ }
256
+ if (isRemoteFileReferencePath(parsed.filePath)) {
257
+ return undefined;
258
+ }
241
259
  const resolution = classifyReferenceUri(targetUri, documents, fileSystem);
242
260
  if (resolution === 'missing') {
243
- return undefined;
261
+ return missingFileLink(sourceRange, targetUri, parsed.filePath);
244
262
  }
245
263
  if (resolution === 'folder') {
246
264
  return {
247
265
  sourceRange,
248
266
  targetUri: targetUri.toString(),
249
267
  resolution,
250
- folderFiles: listFolderFileNames(fileSystem, targetUri)
268
+ folderFiles: listFolderFileNames(fileSystem, targetUri),
269
+ authoredPath: parsed.filePath
251
270
  };
252
271
  }
253
272
  const text = readTargetText(targetUri, documents, fileSystem);
254
273
  if (text === undefined) {
255
- return undefined;
274
+ return missingFileLink(sourceRange, targetUri, parsed.filePath);
256
275
  }
257
276
  const targetIssue = detectFileLinkTargetIssue(text, findTargetDocument(targetUri, documents));
258
277
  let targetRange: Range | undefined;
@@ -271,10 +290,24 @@ function resolvePathStringLink(
271
290
  targetUri: targetUri.toString(),
272
291
  targetRange,
273
292
  resolution: 'file',
274
- targetIssue
293
+ targetIssue,
294
+ authoredPath: parsed.filePath
295
+ };
296
+ }
297
+
298
+ function missingFileLink(sourceRange: Range, targetUri: URI, authoredPath: string): ResolvedFileLink {
299
+ return {
300
+ sourceRange,
301
+ targetUri: targetUri.toString(),
302
+ resolution: 'missing',
303
+ authoredPath
275
304
  };
276
305
  }
277
306
 
307
+ function isRemoteFileReferencePath(path: string): boolean {
308
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(path);
309
+ }
310
+
278
311
  export function detectFileLinkTargetIssue(
279
312
  text: string,
280
313
  targetDocument: LangiumDocument | undefined
@@ -297,6 +330,14 @@ export function fileLinkTargetIssueMessage(issue: FileLinkTargetIssue): string {
297
330
  }
298
331
  }
299
332
 
333
+ export function fileLinkMissingMessage(path: string): string {
334
+ const trimmed = path.trim();
335
+ if (!trimmed) {
336
+ return 'Could not resolve file reference.';
337
+ }
338
+ return `Could not resolve file reference '${trimmed}'.`;
339
+ }
340
+
300
341
  function findTargetDocument(targetUri: URI, documents: LangiumDocuments): LangiumDocument | undefined {
301
342
  const direct = documents.getDocument(targetUri);
302
343
  if (direct) {
@@ -347,7 +388,11 @@ export function resolveImportedFileLink(
347
388
  context?: PathResolveContext
348
389
  ): ResolvedFileLink | undefined {
349
390
  const targetUri = resolveExistingImportUri(filePath, document, documents, fileSystem, context);
350
- return resolvePathStringLink(document, documents, fileSystem, { filePath }, targetUri, sourceRange);
391
+ const link = resolvePathStringLink(document, documents, fileSystem, { filePath }, targetUri, sourceRange);
392
+ if (!link || link.resolution === 'missing') {
393
+ return undefined;
394
+ }
395
+ return link;
351
396
  }
352
397
 
353
398
  export function filePathRangeInStringNode(pathNode: CstNode, parsed: ParsedFileReference): Range {
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Parses file path strings from bracket references, including L# line suffixes and :test name suffixes.
3
+ * rq:["../../../reqlan rq/language/syntax.rq".reference_file]
3
4
  */
4
5
 
5
6
  export interface ParsedFileReference {
@@ -30,28 +31,37 @@ export function parseFileReferenceString(file: string): ParsedFileReference {
30
31
  }
31
32
 
32
33
  function parseTestNameSuffix(file: string): { filePath: string; testName?: string } {
33
- let colonIndex = -1;
34
- for (let index = file.length - 1; index >= 0; index--) {
34
+ for (let index = 0; index < file.length; index++) {
35
35
  if (file[index] !== ':') {
36
36
  continue;
37
37
  }
38
- if (index > 0 && file[index - 1] === '/' && index > 1 && file[index - 2] === '/') {
38
+ if (isUriSchemeColon(file, index) || isWindowsDriveColon(file, index)) {
39
39
  continue;
40
40
  }
41
- colonIndex = index;
42
- break;
43
- }
44
- if (colonIndex < 0) {
45
- return { filePath: file };
46
- }
47
- const testName = file.slice(colonIndex + 1);
48
- if (!testName) {
49
- return { filePath: file };
41
+ const testName = file.slice(index + 1);
42
+ if (!testName) {
43
+ return { filePath: file };
44
+ }
45
+ return {
46
+ filePath: file.slice(0, index),
47
+ testName
48
+ };
50
49
  }
51
- return {
52
- filePath: file.slice(0, colonIndex),
53
- testName
54
- };
50
+ return { filePath: file };
51
+ }
52
+
53
+ /** `https://` / `file://` — the colon that starts `://`. */
54
+ function isUriSchemeColon(file: string, index: number): boolean {
55
+ return file[index + 1] === '/' && file[index + 2] === '/';
56
+ }
57
+
58
+ /** Drive letter in `C:\…` or `file://C:/…`. */
59
+ function isWindowsDriveColon(file: string, index: number): boolean {
60
+ const previous = file[index - 1];
61
+ const next = file[index + 1];
62
+ return previous !== undefined
63
+ && /[A-Za-z]/.test(previous)
64
+ && (next === '\\' || next === '/');
55
65
  }
56
66
 
57
67
  const NON_RQ_FILE_EXTENSION = /\.[^./\\]+$/;