vscode-json-languageservice 5.2.0 → 5.3.0
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/CHANGELOG.md +5 -0
- package/lib/esm/jsonLanguageService.d.ts +3 -2
- package/lib/esm/jsonLanguageService.js +4 -14
- package/lib/esm/jsonLanguageTypes.d.ts +5 -2
- package/lib/esm/services/jsonCompletion.js +4 -4
- package/lib/esm/services/jsonDocumentSymbols.js +6 -2
- package/lib/esm/services/jsonHover.js +1 -1
- package/lib/esm/utils/format.js +20 -0
- package/lib/esm/utils/objects.js +3 -3
- package/lib/esm/utils/propertyTree.js +71 -0
- package/lib/esm/utils/sort.js +355 -0
- package/lib/esm/utils/strings.js +1 -1
- package/lib/umd/jsonLanguageService.d.ts +3 -2
- package/lib/umd/jsonLanguageService.js +5 -15
- package/lib/umd/jsonLanguageTypes.d.ts +5 -2
- package/lib/umd/services/jsonCompletion.js +4 -4
- package/lib/umd/services/jsonDocumentSymbols.js +7 -3
- package/lib/umd/services/jsonHover.js +1 -1
- package/lib/umd/utils/format.js +34 -0
- package/lib/umd/utils/objects.js +3 -3
- package/lib/umd/utils/propertyTree.js +85 -0
- package/lib/umd/utils/sort.js +369 -0
- package/lib/umd/utils/strings.js +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
5.3.0 / 2023-02-15
|
|
2
|
+
================
|
|
3
|
+
* new API `LanguageService.sort` for sorting all properties in a JSON document
|
|
4
|
+
* new API `SortOptions`
|
|
5
|
+
|
|
1
6
|
5.2.0 / 2023-02-02
|
|
2
7
|
================
|
|
3
8
|
* Added `SchemaConfiguration.folderUri` for folder-specific schema associations
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus } from './jsonLanguageTypes';
|
|
1
|
+
import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus, SortOptions } from './jsonLanguageTypes';
|
|
2
2
|
import { DocumentLink } from 'vscode-languageserver-types';
|
|
3
3
|
export type JSONDocument = {
|
|
4
4
|
root: ASTNode | undefined;
|
|
@@ -20,10 +20,11 @@ export interface LanguageService {
|
|
|
20
20
|
findDocumentColors(document: TextDocument, doc: JSONDocument, context?: DocumentColorsContext): Thenable<ColorInformation[]>;
|
|
21
21
|
getColorPresentations(document: TextDocument, doc: JSONDocument, color: Color, range: Range): ColorPresentation[];
|
|
22
22
|
doHover(document: TextDocument, position: Position, doc: JSONDocument): Thenable<Hover | null>;
|
|
23
|
-
format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
|
|
24
23
|
getFoldingRanges(document: TextDocument, context?: FoldingRangesContext): FoldingRange[];
|
|
25
24
|
getSelectionRanges(document: TextDocument, positions: Position[], doc: JSONDocument): SelectionRange[];
|
|
26
25
|
findDefinition(document: TextDocument, position: Position, doc: JSONDocument): Thenable<DefinitionLink[]>;
|
|
27
26
|
findLinks(document: TextDocument, doc: JSONDocument): Thenable<DocumentLink[]>;
|
|
27
|
+
format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
|
|
28
|
+
sort(document: TextDocument, options: SortOptions): TextEdit[];
|
|
28
29
|
}
|
|
29
30
|
export declare function getLanguageService(params: LanguageServiceParams): LanguageService;
|
|
@@ -11,8 +11,8 @@ import { schemaContributions } from './services/configuration';
|
|
|
11
11
|
import { JSONSchemaService } from './services/jsonSchemaService';
|
|
12
12
|
import { getFoldingRanges } from './services/jsonFolding';
|
|
13
13
|
import { getSelectionRanges } from './services/jsonSelectionRanges';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { sort } from './utils/sort';
|
|
15
|
+
import { format } from './utils/format';
|
|
16
16
|
import { findLinks } from './services/jsonLinks';
|
|
17
17
|
export * from './jsonLanguageTypes';
|
|
18
18
|
export function getLanguageService(params) {
|
|
@@ -46,17 +46,7 @@ export function getLanguageService(params) {
|
|
|
46
46
|
getSelectionRanges,
|
|
47
47
|
findDefinition: () => Promise.resolve([]),
|
|
48
48
|
findLinks,
|
|
49
|
-
format: (
|
|
50
|
-
|
|
51
|
-
if (r) {
|
|
52
|
-
const offset = d.offsetAt(r.start);
|
|
53
|
-
const length = d.offsetAt(r.end) - offset;
|
|
54
|
-
range = { offset, length };
|
|
55
|
-
}
|
|
56
|
-
const options = { tabSize: o ? o.tabSize : 4, insertSpaces: o?.insertSpaces === true, insertFinalNewline: o?.insertFinalNewline === true, eol: '\n', keepLines: o?.keepLines === true };
|
|
57
|
-
return formatJSON(d.getText(), range, options).map(e => {
|
|
58
|
-
return TextEdit.replace(Range.create(d.positionAt(e.offset), d.positionAt(e.offset + e.length)), e.content);
|
|
59
|
-
});
|
|
60
|
-
}
|
|
49
|
+
format: (document, range, options) => format(document, options, range),
|
|
50
|
+
sort: (document, options) => sort(document, options)
|
|
61
51
|
};
|
|
62
52
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { JSONWorkerContribution, JSONPath, Segment, CompletionsCollector } from './jsonContributions';
|
|
2
2
|
import { JSONSchema } from './jsonSchema';
|
|
3
3
|
import { Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, FormattingOptions as LSPFormattingOptions, DefinitionLink, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind } from 'vscode-languageserver-types';
|
|
4
|
-
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
5
|
-
export { TextDocument, Range, Position, DocumentUri, MarkupContent, MarkupKind, JSONSchema, JSONWorkerContribution, JSONPath, Segment, CompletionsCollector, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind };
|
|
4
|
+
import { TextDocument, TextDocumentContentChangeEvent } from 'vscode-languageserver-textdocument';
|
|
5
|
+
export { TextDocument, TextDocumentContentChangeEvent, Range, Position, DocumentUri, MarkupContent, MarkupKind, JSONSchema, JSONWorkerContribution, JSONPath, Segment, CompletionsCollector, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind };
|
|
6
6
|
/**
|
|
7
7
|
* Error codes used by diagnostics
|
|
8
8
|
*/
|
|
@@ -295,3 +295,6 @@ export interface FormattingOptions extends LSPFormattingOptions {
|
|
|
295
295
|
insertFinalNewline?: boolean;
|
|
296
296
|
keepLines?: boolean;
|
|
297
297
|
}
|
|
298
|
+
export interface SortOptions extends LSPFormattingOptions {
|
|
299
|
+
insertFinalNewline?: boolean;
|
|
300
|
+
}
|
|
@@ -687,7 +687,7 @@ export class JSONCompletion {
|
|
|
687
687
|
return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
|
|
688
688
|
}
|
|
689
689
|
getInsertTextForValue(value, separatorAfter) {
|
|
690
|
-
|
|
690
|
+
const text = JSON.stringify(value, null, '\t');
|
|
691
691
|
if (text === '{}') {
|
|
692
692
|
return '{$1}' + separatorAfter;
|
|
693
693
|
}
|
|
@@ -805,7 +805,7 @@ export class JSONCompletion {
|
|
|
805
805
|
nValueProposals += propertySchema.examples.length;
|
|
806
806
|
}
|
|
807
807
|
if (nValueProposals === 0) {
|
|
808
|
-
|
|
808
|
+
let type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
|
|
809
809
|
if (!type) {
|
|
810
810
|
if (propertySchema.properties) {
|
|
811
811
|
type = 'object';
|
|
@@ -845,8 +845,8 @@ export class JSONCompletion {
|
|
|
845
845
|
return resultText + value + separatorAfter;
|
|
846
846
|
}
|
|
847
847
|
getCurrentWord(document, offset) {
|
|
848
|
-
|
|
849
|
-
|
|
848
|
+
let i = offset - 1;
|
|
849
|
+
const text = document.getText();
|
|
850
850
|
while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
|
|
851
851
|
i--;
|
|
852
852
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import * as Parser from '../parser/jsonParser';
|
|
6
6
|
import * as Strings from '../utils/strings';
|
|
7
7
|
import { colorFromHex } from '../utils/colors';
|
|
8
|
+
import * as l10n from '@vscode/l10n';
|
|
8
9
|
import { Range, TextEdit, SymbolKind, Location } from "../jsonLanguageTypes";
|
|
9
10
|
export class JSONDocumentSymbols {
|
|
10
11
|
constructor(schemaService) {
|
|
@@ -26,7 +27,7 @@ export class JSONDocumentSymbols {
|
|
|
26
27
|
for (const property of item.properties) {
|
|
27
28
|
if (property.keyNode.value === 'key' && property.valueNode) {
|
|
28
29
|
const location = Location.create(document.uri, getRange(document, item));
|
|
29
|
-
result.push({ name:
|
|
30
|
+
result.push({ name: getName(property.valueNode), kind: SymbolKind.Function, location: location });
|
|
30
31
|
limit--;
|
|
31
32
|
if (limit <= 0) {
|
|
32
33
|
if (context && context.onResultLimitExceeded) {
|
|
@@ -100,7 +101,7 @@ export class JSONDocumentSymbols {
|
|
|
100
101
|
if (property.keyNode.value === 'key' && property.valueNode) {
|
|
101
102
|
const range = getRange(document, item);
|
|
102
103
|
const selectionRange = getRange(document, property.keyNode);
|
|
103
|
-
result.push({ name:
|
|
104
|
+
result.push({ name: getName(property.valueNode), kind: SymbolKind.Function, range, selectionRange });
|
|
104
105
|
limit--;
|
|
105
106
|
if (limit <= 0) {
|
|
106
107
|
if (context && context.onResultLimitExceeded) {
|
|
@@ -265,3 +266,6 @@ export class JSONDocumentSymbols {
|
|
|
265
266
|
function getRange(document, node) {
|
|
266
267
|
return Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
|
|
267
268
|
}
|
|
269
|
+
function getName(node) {
|
|
270
|
+
return Parser.getNodeValue(node) || l10n.t('<empty>');
|
|
271
|
+
}
|
|
@@ -28,7 +28,7 @@ export class JSONHover {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const hoverRange = Range.create(document.positionAt(hoverRangeNode.offset), document.positionAt(hoverRangeNode.offset + hoverRangeNode.length));
|
|
31
|
-
|
|
31
|
+
const createHover = (contents) => {
|
|
32
32
|
const result = {
|
|
33
33
|
contents: contents,
|
|
34
34
|
range: hoverRange
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { format as formatJSON } from 'jsonc-parser';
|
|
2
|
+
import { Range, TextEdit } from '../jsonLanguageTypes';
|
|
3
|
+
export function format(documentToFormat, formattingOptions, formattingRange) {
|
|
4
|
+
let range = undefined;
|
|
5
|
+
if (formattingRange) {
|
|
6
|
+
const offset = documentToFormat.offsetAt(formattingRange.start);
|
|
7
|
+
const length = documentToFormat.offsetAt(formattingRange.end) - offset;
|
|
8
|
+
range = { offset, length };
|
|
9
|
+
}
|
|
10
|
+
const options = {
|
|
11
|
+
tabSize: formattingOptions ? formattingOptions.tabSize : 4,
|
|
12
|
+
insertSpaces: formattingOptions?.insertSpaces === true,
|
|
13
|
+
insertFinalNewline: formattingOptions?.insertFinalNewline === true,
|
|
14
|
+
eol: '\n',
|
|
15
|
+
keepLines: formattingOptions?.keepLines === true
|
|
16
|
+
};
|
|
17
|
+
return formatJSON(documentToFormat.getText(), range, options).map(edit => {
|
|
18
|
+
return TextEdit.replace(Range.create(documentToFormat.positionAt(edit.offset), documentToFormat.positionAt(edit.offset + edit.length)), edit.content);
|
|
19
|
+
});
|
|
20
|
+
}
|
package/lib/esm/utils/objects.js
CHANGED
|
@@ -18,7 +18,7 @@ export function equals(one, other) {
|
|
|
18
18
|
if ((Array.isArray(one)) !== (Array.isArray(other))) {
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
let i, key;
|
|
22
22
|
if (Array.isArray(one)) {
|
|
23
23
|
if (one.length !== other.length) {
|
|
24
24
|
return false;
|
|
@@ -30,12 +30,12 @@ export function equals(one, other) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
|
-
|
|
33
|
+
const oneKeys = [];
|
|
34
34
|
for (key in one) {
|
|
35
35
|
oneKeys.push(key);
|
|
36
36
|
}
|
|
37
37
|
oneKeys.sort();
|
|
38
|
-
|
|
38
|
+
const otherKeys = [];
|
|
39
39
|
for (key in other) {
|
|
40
40
|
otherKeys.push(key);
|
|
41
41
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
export var Container;
|
|
6
|
+
(function (Container) {
|
|
7
|
+
Container[Container["Object"] = 0] = "Object";
|
|
8
|
+
Container[Container["Array"] = 1] = "Array";
|
|
9
|
+
})(Container || (Container = {}));
|
|
10
|
+
export class PropertyTree {
|
|
11
|
+
constructor(propertyName, beginningLineNumber) {
|
|
12
|
+
this.propertyName = propertyName ?? '';
|
|
13
|
+
this.beginningLineNumber = beginningLineNumber;
|
|
14
|
+
this.childrenProperties = [];
|
|
15
|
+
this.lastProperty = false;
|
|
16
|
+
this.noKeyName = false;
|
|
17
|
+
}
|
|
18
|
+
addChildProperty(childProperty) {
|
|
19
|
+
childProperty.parent = this;
|
|
20
|
+
if (this.childrenProperties.length > 0) {
|
|
21
|
+
let insertionIndex = 0;
|
|
22
|
+
if (childProperty.noKeyName) {
|
|
23
|
+
insertionIndex = this.childrenProperties.length;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
insertionIndex = binarySearchOnPropertyArray(this.childrenProperties, childProperty, compareProperties);
|
|
27
|
+
}
|
|
28
|
+
if (insertionIndex < 0) {
|
|
29
|
+
insertionIndex = (insertionIndex * -1) - 1;
|
|
30
|
+
}
|
|
31
|
+
this.childrenProperties.splice(insertionIndex, 0, childProperty);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.childrenProperties.push(childProperty);
|
|
35
|
+
}
|
|
36
|
+
return childProperty;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function compareProperties(propertyTree1, propertyTree2) {
|
|
40
|
+
if (propertyTree1.propertyName < propertyTree2.propertyName) {
|
|
41
|
+
return -1;
|
|
42
|
+
}
|
|
43
|
+
else if (propertyTree1.propertyName > propertyTree2.propertyName) {
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
function binarySearchOnPropertyArray(propertyTreeArray, propertyTree, compare_fn) {
|
|
49
|
+
if (propertyTree.propertyName < propertyTreeArray[0].propertyName) {
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
if (propertyTree.propertyName > propertyTreeArray[propertyTreeArray.length - 1].propertyName) {
|
|
53
|
+
return propertyTreeArray.length;
|
|
54
|
+
}
|
|
55
|
+
let m = 0;
|
|
56
|
+
let n = propertyTreeArray.length - 1;
|
|
57
|
+
while (m <= n) {
|
|
58
|
+
let k = (n + m) >> 1;
|
|
59
|
+
let cmp = compare_fn(propertyTree, propertyTreeArray[k]);
|
|
60
|
+
if (cmp > 0) {
|
|
61
|
+
m = k + 1;
|
|
62
|
+
}
|
|
63
|
+
else if (cmp < 0) {
|
|
64
|
+
n = k - 1;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return k;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return -m - 1;
|
|
71
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/*---------------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
// import { TextEdit} from 'vscode-languageserver-textdocument';
|
|
6
|
+
import { createScanner } from 'jsonc-parser';
|
|
7
|
+
import { TextDocument, TextEdit, Position, Range } from '../jsonLanguageTypes';
|
|
8
|
+
import { format } from './format';
|
|
9
|
+
import { PropertyTree, Container } from './propertyTree';
|
|
10
|
+
export function sort(documentToSort, formattingOptions) {
|
|
11
|
+
const options = {
|
|
12
|
+
...formattingOptions,
|
|
13
|
+
keepLines: false, // keepLines must be false so that the properties are on separate lines for the sorting
|
|
14
|
+
};
|
|
15
|
+
const formattedJsonString = TextDocument.applyEdits(documentToSort, format(documentToSort, options, undefined));
|
|
16
|
+
const formattedJsonDocument = TextDocument.create('test://test.json', 'json', 0, formattedJsonString);
|
|
17
|
+
const jsonPropertyTree = findJsoncPropertyTree(formattedJsonDocument);
|
|
18
|
+
const sortedJsonDocument = sortJsoncDocument(formattedJsonDocument, jsonPropertyTree);
|
|
19
|
+
const edits = format(sortedJsonDocument, options, undefined);
|
|
20
|
+
const sortedAndFormattedJsonDocument = TextDocument.applyEdits(sortedJsonDocument, edits);
|
|
21
|
+
return [TextEdit.replace(Range.create(Position.create(0, 0), documentToSort.positionAt(documentToSort.getText().length)), sortedAndFormattedJsonDocument)];
|
|
22
|
+
}
|
|
23
|
+
function findJsoncPropertyTree(formattedDocument) {
|
|
24
|
+
const formattedString = formattedDocument.getText();
|
|
25
|
+
const scanner = createScanner(formattedString, false);
|
|
26
|
+
// The tree that will be returned
|
|
27
|
+
let rootTree = new PropertyTree();
|
|
28
|
+
// The tree where the current properties can be added as children
|
|
29
|
+
let currentTree = rootTree;
|
|
30
|
+
// The tree representing the current property analyzed
|
|
31
|
+
let currentProperty = rootTree;
|
|
32
|
+
// The tree representing the previous property analyzed
|
|
33
|
+
let lastProperty = rootTree;
|
|
34
|
+
// The current scanned token
|
|
35
|
+
let token = undefined;
|
|
36
|
+
// Line number of the last token found
|
|
37
|
+
let lastTokenLine = 0;
|
|
38
|
+
// Total number of characters on the lines prior to current line
|
|
39
|
+
let numberOfCharactersOnPreviousLines = 0;
|
|
40
|
+
// The last token scanned that is not trivial, nor a comment
|
|
41
|
+
let lastNonTriviaNonCommentToken = undefined;
|
|
42
|
+
// The second to last token scanned that is not trivial, nor a comment
|
|
43
|
+
let secondToLastNonTriviaNonCommentToken = undefined;
|
|
44
|
+
// Line number of last token that is not trivial, nor a comment
|
|
45
|
+
let lineOfLastNonTriviaNonCommentToken = -1;
|
|
46
|
+
// End index on its line of last token that is not trivial, nor a comment
|
|
47
|
+
let endIndexOfLastNonTriviaNonCommentToken = -1;
|
|
48
|
+
// Line number of the start of the range of current/next property
|
|
49
|
+
let beginningLineNumber = 0;
|
|
50
|
+
// Line number of the end of the range of current/next property
|
|
51
|
+
let endLineNumber = 0;
|
|
52
|
+
// Stack indicating whether we are inside of an object or an array
|
|
53
|
+
let currentContainerStack = [];
|
|
54
|
+
// Boolean indicating that the current property end line number needs to be updated. Used only when block comments are encountered.
|
|
55
|
+
let updateLastPropertyEndLineNumber = false;
|
|
56
|
+
// Boolean indicating that the beginning line number should be updated. Used only when block comments are encountered.
|
|
57
|
+
let updateBeginningLineNumber = false;
|
|
58
|
+
while ((token = scanner.scan()) !== 17 /* SyntaxKind.EOF */) {
|
|
59
|
+
// In the case when a block comment has been encountered that starts on the same line as the comma ending a property, update the end line of that
|
|
60
|
+
// property so that it covers the block comment. For example, if we have:
|
|
61
|
+
// 1. "key" : {}, /* some block
|
|
62
|
+
// 2. comment */
|
|
63
|
+
// Then, the end line of the property "key" should be line 2 not line 1
|
|
64
|
+
if (updateLastPropertyEndLineNumber === true
|
|
65
|
+
&& token !== 14 /* SyntaxKind.LineBreakTrivia */
|
|
66
|
+
&& token !== 15 /* SyntaxKind.Trivia */
|
|
67
|
+
&& token !== 12 /* SyntaxKind.LineCommentTrivia */
|
|
68
|
+
&& token !== 13 /* SyntaxKind.BlockCommentTrivia */
|
|
69
|
+
&& currentProperty.endLineNumber === undefined) {
|
|
70
|
+
let endLineNumber = scanner.getTokenStartLine();
|
|
71
|
+
// Update the end line number in the case when the last property visited is a container (object or array)
|
|
72
|
+
if (secondToLastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */
|
|
73
|
+
|| secondToLastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */) {
|
|
74
|
+
lastProperty.endLineNumber = endLineNumber - 1;
|
|
75
|
+
}
|
|
76
|
+
// Update the end line number in the case when the last property visited is a simple property
|
|
77
|
+
else {
|
|
78
|
+
currentProperty.endLineNumber = endLineNumber - 1;
|
|
79
|
+
}
|
|
80
|
+
beginningLineNumber = endLineNumber;
|
|
81
|
+
updateLastPropertyEndLineNumber = false;
|
|
82
|
+
}
|
|
83
|
+
// When a block comment follows an open brace or an open bracket, that block comment should be associated to that brace or bracket, not the property below it. For example, for:
|
|
84
|
+
// 1. { /*
|
|
85
|
+
// 2. ... */
|
|
86
|
+
// 3. "key" : {}
|
|
87
|
+
// 4. }
|
|
88
|
+
// Instead of associating the block comment to the property on line 3, it is associate to the property on line 1
|
|
89
|
+
if (updateBeginningLineNumber === true
|
|
90
|
+
&& token !== 14 /* SyntaxKind.LineBreakTrivia */
|
|
91
|
+
&& token !== 15 /* SyntaxKind.Trivia */
|
|
92
|
+
&& token !== 12 /* SyntaxKind.LineCommentTrivia */
|
|
93
|
+
&& token !== 13 /* SyntaxKind.BlockCommentTrivia */) {
|
|
94
|
+
beginningLineNumber = scanner.getTokenStartLine();
|
|
95
|
+
updateBeginningLineNumber = false;
|
|
96
|
+
}
|
|
97
|
+
// Update the number of characters on all the previous lines each time the new token is on a different line to the previous token
|
|
98
|
+
if (scanner.getTokenStartLine() !== lastTokenLine) {
|
|
99
|
+
for (let i = lastTokenLine; i < scanner.getTokenStartLine(); i++) {
|
|
100
|
+
const lengthOfLine = formattedDocument.getText(Range.create(Position.create(i, 0), Position.create(i + 1, 0))).length;
|
|
101
|
+
numberOfCharactersOnPreviousLines = numberOfCharactersOnPreviousLines + lengthOfLine;
|
|
102
|
+
}
|
|
103
|
+
lastTokenLine = scanner.getTokenStartLine();
|
|
104
|
+
}
|
|
105
|
+
switch (token) {
|
|
106
|
+
// When a string is found, if it follows an open brace or a comma token and it is within an object, then it corresponds to a key name, not a simple string
|
|
107
|
+
case 10 /* SyntaxKind.StringLiteral */: {
|
|
108
|
+
if ((lastNonTriviaNonCommentToken === undefined
|
|
109
|
+
|| lastNonTriviaNonCommentToken === 1 /* SyntaxKind.OpenBraceToken */
|
|
110
|
+
|| (lastNonTriviaNonCommentToken === 5 /* SyntaxKind.CommaToken */
|
|
111
|
+
&& currentContainerStack[currentContainerStack.length - 1] === Container.Object))) {
|
|
112
|
+
// In that case create the child property which starts at beginningLineNumber, add it to the current tree
|
|
113
|
+
const childProperty = new PropertyTree(scanner.getTokenValue(), beginningLineNumber);
|
|
114
|
+
lastProperty = currentProperty;
|
|
115
|
+
currentProperty = currentTree.addChildProperty(childProperty);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
// When the token is an open bracket, then we enter into an array
|
|
120
|
+
case 3 /* SyntaxKind.OpenBracketToken */: {
|
|
121
|
+
// If the root tree beginning line number is not defined, then this open bracket is the first open bracket in the document
|
|
122
|
+
if (rootTree.beginningLineNumber === undefined) {
|
|
123
|
+
rootTree.beginningLineNumber = scanner.getTokenStartLine();
|
|
124
|
+
}
|
|
125
|
+
// Suppose we are inside of an object, then the current array is associated to a key, and has already been created
|
|
126
|
+
// We have the following configuration: {"a": "val", "array": [...], "b": "val"}
|
|
127
|
+
// In that case navigate down to the child property
|
|
128
|
+
if (currentContainerStack[currentContainerStack.length - 1] === Container.Object) {
|
|
129
|
+
currentTree = currentProperty;
|
|
130
|
+
}
|
|
131
|
+
// Suppose we are inside of an array, then since the current array is not associated to a key, it has not been created yet
|
|
132
|
+
// We have the following configuration: ["a", [...], "b"]
|
|
133
|
+
// In that case create the property and navigate down
|
|
134
|
+
else if (currentContainerStack[currentContainerStack.length - 1] === Container.Array) {
|
|
135
|
+
const childProperty = new PropertyTree(scanner.getTokenValue(), beginningLineNumber);
|
|
136
|
+
childProperty.noKeyName = true;
|
|
137
|
+
lastProperty = currentProperty;
|
|
138
|
+
currentProperty = currentTree.addChildProperty(childProperty);
|
|
139
|
+
}
|
|
140
|
+
currentContainerStack.push(Container.Array);
|
|
141
|
+
currentProperty.type = Container.Array;
|
|
142
|
+
beginningLineNumber = scanner.getTokenStartLine();
|
|
143
|
+
beginningLineNumber++;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
// When the token is an open brace, then we enter into an object
|
|
147
|
+
case 1 /* SyntaxKind.OpenBraceToken */: {
|
|
148
|
+
// If the root tree beginning line number is not defined, then this open brace is the first open brace in the document
|
|
149
|
+
if (rootTree.beginningLineNumber === undefined) {
|
|
150
|
+
rootTree.beginningLineNumber = scanner.getTokenStartLine();
|
|
151
|
+
}
|
|
152
|
+
// 1. If we are inside of an objet, the current object is associated to a key and has already been created
|
|
153
|
+
// We have the following configuration: {"a": "val", "object": {...}, "b": "val"}
|
|
154
|
+
// 2. Otherwise the current object property is inside of an array, not associated to a key name and the property has not yet been created
|
|
155
|
+
// We have the following configuration: ["a", {...}, "b"]
|
|
156
|
+
else if (currentContainerStack[currentContainerStack.length - 1] === Container.Array) {
|
|
157
|
+
const childProperty = new PropertyTree(scanner.getTokenValue(), beginningLineNumber);
|
|
158
|
+
childProperty.noKeyName = true;
|
|
159
|
+
lastProperty = currentProperty;
|
|
160
|
+
currentProperty = currentTree.addChildProperty(childProperty);
|
|
161
|
+
}
|
|
162
|
+
currentProperty.type = Container.Object;
|
|
163
|
+
currentContainerStack.push(Container.Object);
|
|
164
|
+
currentTree = currentProperty;
|
|
165
|
+
beginningLineNumber = scanner.getTokenStartLine();
|
|
166
|
+
beginningLineNumber++;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 4 /* SyntaxKind.CloseBracketToken */: {
|
|
170
|
+
endLineNumber = scanner.getTokenStartLine();
|
|
171
|
+
currentContainerStack.pop();
|
|
172
|
+
// If the last non-trivial non-comment token is a closing brace or bracket, then the currentProperty end line number has not been set yet so set it
|
|
173
|
+
// The configuration considered is: [..., {}] or [..., []]
|
|
174
|
+
if (currentProperty.endLineNumber === undefined
|
|
175
|
+
&& (lastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */
|
|
176
|
+
|| lastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */)) {
|
|
177
|
+
currentProperty.endLineNumber = endLineNumber - 1;
|
|
178
|
+
currentProperty.lastProperty = true;
|
|
179
|
+
currentProperty.lineWhereToAddComma = lineOfLastNonTriviaNonCommentToken;
|
|
180
|
+
currentProperty.indexWhereToAddComa = endIndexOfLastNonTriviaNonCommentToken;
|
|
181
|
+
lastProperty = currentProperty;
|
|
182
|
+
currentProperty = currentProperty ? currentProperty.parent : undefined;
|
|
183
|
+
currentTree = currentProperty;
|
|
184
|
+
}
|
|
185
|
+
rootTree.endLineNumber = endLineNumber;
|
|
186
|
+
beginningLineNumber = endLineNumber + 1;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case 2 /* SyntaxKind.CloseBraceToken */: {
|
|
190
|
+
endLineNumber = scanner.getTokenStartLine();
|
|
191
|
+
currentContainerStack.pop();
|
|
192
|
+
// If we are not inside of an empty object and current property end line number has not yet been defined, define it
|
|
193
|
+
if (lastNonTriviaNonCommentToken !== 1 /* SyntaxKind.OpenBraceToken */
|
|
194
|
+
&& currentProperty.endLineNumber === undefined) {
|
|
195
|
+
currentProperty.endLineNumber = endLineNumber - 1;
|
|
196
|
+
// The current property is also the last property
|
|
197
|
+
currentProperty.lastProperty = true;
|
|
198
|
+
// The last property of an object is associated with the line and index of where to add the comma, in case after sorting, it is no longer the last property
|
|
199
|
+
currentProperty.lineWhereToAddComma = lineOfLastNonTriviaNonCommentToken;
|
|
200
|
+
currentProperty.indexWhereToAddComa = endIndexOfLastNonTriviaNonCommentToken;
|
|
201
|
+
lastProperty = currentProperty;
|
|
202
|
+
currentProperty = currentProperty ? currentProperty.parent : undefined;
|
|
203
|
+
currentTree = currentProperty;
|
|
204
|
+
}
|
|
205
|
+
rootTree.endLineNumber = scanner.getTokenStartLine();
|
|
206
|
+
beginningLineNumber = endLineNumber + 1;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 5 /* SyntaxKind.CommaToken */: {
|
|
210
|
+
endLineNumber = scanner.getTokenStartLine();
|
|
211
|
+
// If the current container is an object or the current container is an array and the last non-trivia non-comment token is a closing brace or a closing bracket
|
|
212
|
+
// Then update the end line number of the current property
|
|
213
|
+
if (currentProperty.endLineNumber === undefined
|
|
214
|
+
&& (currentContainerStack[currentContainerStack.length - 1] === Container.Object
|
|
215
|
+
|| (currentContainerStack[currentContainerStack.length - 1] === Container.Array
|
|
216
|
+
&& (lastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */
|
|
217
|
+
|| lastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */)))) {
|
|
218
|
+
currentProperty.endLineNumber = endLineNumber;
|
|
219
|
+
// Store the line and the index of the comma in case it needs to be removed during the sorting
|
|
220
|
+
currentProperty.commaIndex = scanner.getTokenOffset() - numberOfCharactersOnPreviousLines;
|
|
221
|
+
currentProperty.commaLine = endLineNumber;
|
|
222
|
+
}
|
|
223
|
+
if (lastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */
|
|
224
|
+
|| lastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */) {
|
|
225
|
+
lastProperty = currentProperty;
|
|
226
|
+
currentProperty = currentProperty ? currentProperty.parent : undefined;
|
|
227
|
+
currentTree = currentProperty;
|
|
228
|
+
}
|
|
229
|
+
beginningLineNumber = endLineNumber + 1;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case 13 /* SyntaxKind.BlockCommentTrivia */: {
|
|
233
|
+
// If the last non trivia non-comment token is a comma and the block comment starts on the same line as the comma, then update the end line number of the current property. For example if:
|
|
234
|
+
// 1. {}, /* ...
|
|
235
|
+
// 2. ..*/
|
|
236
|
+
// The the property on line 1 shoud end on line 2, not line 1
|
|
237
|
+
// In the case we are in an array we update the end line number only if the second to last non-trivia non-comment token is a closing brace or bracket
|
|
238
|
+
if (lastNonTriviaNonCommentToken === 5 /* SyntaxKind.CommaToken */
|
|
239
|
+
&& lineOfLastNonTriviaNonCommentToken === scanner.getTokenStartLine()
|
|
240
|
+
&& (currentContainerStack[currentContainerStack.length - 1] === Container.Array
|
|
241
|
+
&& (secondToLastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */
|
|
242
|
+
|| secondToLastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */)
|
|
243
|
+
|| currentContainerStack[currentContainerStack.length - 1] === Container.Object)) {
|
|
244
|
+
if (currentContainerStack[currentContainerStack.length - 1] === Container.Array && (secondToLastNonTriviaNonCommentToken === 2 /* SyntaxKind.CloseBraceToken */ || secondToLastNonTriviaNonCommentToken === 4 /* SyntaxKind.CloseBracketToken */) || currentContainerStack[currentContainerStack.length - 1] === Container.Object) {
|
|
245
|
+
currentProperty.endLineNumber = undefined;
|
|
246
|
+
updateLastPropertyEndLineNumber = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// When the block comment follows an open brace or an open token, we have the following scenario:
|
|
250
|
+
// { /**
|
|
251
|
+
// ../
|
|
252
|
+
// }
|
|
253
|
+
// The block comment should be assigned to the open brace not the first property below it
|
|
254
|
+
if ((lastNonTriviaNonCommentToken === 1 /* SyntaxKind.OpenBraceToken */
|
|
255
|
+
|| lastNonTriviaNonCommentToken === 3 /* SyntaxKind.OpenBracketToken */)
|
|
256
|
+
&& lineOfLastNonTriviaNonCommentToken === scanner.getTokenStartLine()) {
|
|
257
|
+
updateBeginningLineNumber = true;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Update the last and second to last non-trivia non-comment tokens
|
|
263
|
+
if (token !== 14 /* SyntaxKind.LineBreakTrivia */
|
|
264
|
+
&& token !== 13 /* SyntaxKind.BlockCommentTrivia */
|
|
265
|
+
&& token !== 12 /* SyntaxKind.LineCommentTrivia */
|
|
266
|
+
&& token !== 15 /* SyntaxKind.Trivia */) {
|
|
267
|
+
secondToLastNonTriviaNonCommentToken = lastNonTriviaNonCommentToken;
|
|
268
|
+
lastNonTriviaNonCommentToken = token;
|
|
269
|
+
lineOfLastNonTriviaNonCommentToken = scanner.getTokenStartLine();
|
|
270
|
+
endIndexOfLastNonTriviaNonCommentToken = scanner.getTokenOffset() + scanner.getTokenLength() - numberOfCharactersOnPreviousLines;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return rootTree;
|
|
274
|
+
}
|
|
275
|
+
function sortJsoncDocument(jsonDocument, propertyTree) {
|
|
276
|
+
if (propertyTree.childrenProperties.length === 0) {
|
|
277
|
+
return jsonDocument;
|
|
278
|
+
}
|
|
279
|
+
const sortedJsonDocument = TextDocument.create('test://test.json', 'json', 0, jsonDocument.getText());
|
|
280
|
+
const queueToSort = [];
|
|
281
|
+
updateSortingQueue(queueToSort, propertyTree, propertyTree.beginningLineNumber);
|
|
282
|
+
while (queueToSort.length > 0) {
|
|
283
|
+
const dataToSort = queueToSort.shift();
|
|
284
|
+
const propertyTreeArray = dataToSort.propertyTreeArray;
|
|
285
|
+
let beginningLineNumber = dataToSort.beginningLineNumber;
|
|
286
|
+
for (let i = 0; i < propertyTreeArray.length; i++) {
|
|
287
|
+
const propertyTree = propertyTreeArray[i];
|
|
288
|
+
const range = Range.create(Position.create(propertyTree.beginningLineNumber, 0), Position.create(propertyTree.endLineNumber + 1, 0));
|
|
289
|
+
const jsonContentToReplace = jsonDocument.getText(range);
|
|
290
|
+
const jsonDocumentToReplace = TextDocument.create('test://test.json', 'json', 0, jsonContentToReplace);
|
|
291
|
+
if (propertyTree.lastProperty === true && i !== propertyTreeArray.length - 1) {
|
|
292
|
+
const lineWhereToAddComma = propertyTree.lineWhereToAddComma - propertyTree.beginningLineNumber;
|
|
293
|
+
const indexWhereToAddComma = propertyTree.indexWhereToAddComa;
|
|
294
|
+
const edit = {
|
|
295
|
+
range: Range.create(Position.create(lineWhereToAddComma, indexWhereToAddComma), Position.create(lineWhereToAddComma, indexWhereToAddComma)),
|
|
296
|
+
text: ','
|
|
297
|
+
};
|
|
298
|
+
TextDocument.update(jsonDocumentToReplace, [edit], 1);
|
|
299
|
+
}
|
|
300
|
+
else if (propertyTree.lastProperty === false && i === propertyTreeArray.length - 1) {
|
|
301
|
+
const commaIndex = propertyTree.commaIndex;
|
|
302
|
+
const commaLine = propertyTree.commaLine;
|
|
303
|
+
const lineWhereToRemoveComma = commaLine - propertyTree.beginningLineNumber;
|
|
304
|
+
const edit = {
|
|
305
|
+
range: Range.create(Position.create(lineWhereToRemoveComma, commaIndex), Position.create(lineWhereToRemoveComma, commaIndex + 1)),
|
|
306
|
+
text: ''
|
|
307
|
+
};
|
|
308
|
+
TextDocument.update(jsonDocumentToReplace, [edit], 1);
|
|
309
|
+
}
|
|
310
|
+
const length = propertyTree.endLineNumber - propertyTree.beginningLineNumber + 1;
|
|
311
|
+
const edit = {
|
|
312
|
+
range: Range.create(Position.create(beginningLineNumber, 0), Position.create(beginningLineNumber + length, 0)),
|
|
313
|
+
text: jsonDocumentToReplace.getText()
|
|
314
|
+
};
|
|
315
|
+
TextDocument.update(sortedJsonDocument, [edit], 1);
|
|
316
|
+
updateSortingQueue(queueToSort, propertyTree, beginningLineNumber);
|
|
317
|
+
beginningLineNumber = beginningLineNumber + length;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return sortedJsonDocument;
|
|
321
|
+
}
|
|
322
|
+
function updateSortingQueue(queue, propertyTree, beginningLineNumber) {
|
|
323
|
+
if (propertyTree.childrenProperties.length === 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (propertyTree.type === Container.Object) {
|
|
327
|
+
let minimumBeginningLineNumber = Infinity;
|
|
328
|
+
for (const childProperty of propertyTree.childrenProperties) {
|
|
329
|
+
if (childProperty.beginningLineNumber < minimumBeginningLineNumber) {
|
|
330
|
+
minimumBeginningLineNumber = childProperty.beginningLineNumber;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const diff = minimumBeginningLineNumber - propertyTree.beginningLineNumber;
|
|
334
|
+
beginningLineNumber = beginningLineNumber + diff;
|
|
335
|
+
queue.push(new SortingRange(beginningLineNumber, propertyTree.childrenProperties));
|
|
336
|
+
}
|
|
337
|
+
else if (propertyTree.type === Container.Array) {
|
|
338
|
+
for (const subObject of propertyTree.childrenProperties) {
|
|
339
|
+
let minimumBeginningLineNumber = Infinity;
|
|
340
|
+
for (const childProperty of subObject.childrenProperties) {
|
|
341
|
+
if (childProperty.beginningLineNumber < minimumBeginningLineNumber) {
|
|
342
|
+
minimumBeginningLineNumber = childProperty.beginningLineNumber;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const diff = minimumBeginningLineNumber - subObject.beginningLineNumber;
|
|
346
|
+
queue.push(new SortingRange(beginningLineNumber + subObject.beginningLineNumber - propertyTree.beginningLineNumber + diff, subObject.childrenProperties));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
class SortingRange {
|
|
351
|
+
constructor(beginningLineNumber, propertyTreeArray) {
|
|
352
|
+
this.beginningLineNumber = beginningLineNumber;
|
|
353
|
+
this.propertyTreeArray = propertyTreeArray;
|
|
354
|
+
}
|
|
355
|
+
}
|
package/lib/esm/utils/strings.js
CHANGED
|
@@ -32,7 +32,7 @@ export function convertSimple2RegExpPattern(pattern) {
|
|
|
32
32
|
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
|
33
33
|
}
|
|
34
34
|
export function repeat(value, count) {
|
|
35
|
-
|
|
35
|
+
let s = '';
|
|
36
36
|
while (count > 0) {
|
|
37
37
|
if ((count & 1) === 1) {
|
|
38
38
|
s += value;
|