mirror of
https://github.com/danbulant/monaco-yaml
synced 2026-06-19 06:21:04 +00:00
Import external libs
This commit is contained in:
parent
ff043657bd
commit
39dcb0e50b
56 changed files with 8385 additions and 3 deletions
22
gulpfile.js
22
gulpfile.js
|
|
@ -59,8 +59,24 @@ gulp.task('release', ['clean-release','compile'], function() {
|
|||
'vs/language/json': __dirname + '/out'
|
||||
},
|
||||
packages: [{
|
||||
name: 'vscode-yaml-languageservice',
|
||||
location: __dirname + '/out/vscode-yaml-languageservice/',
|
||||
main: 'yamlLanguageService'
|
||||
}, {
|
||||
name: 'yaml-language-server',
|
||||
location: __dirname + '/out/yaml-language-server',
|
||||
main: 'yamlLanguageService'
|
||||
}, {
|
||||
name: 'yaml-ast-parser',
|
||||
location: __dirname + '/out/yaml-ast-parser',
|
||||
main: 'index'
|
||||
}, {
|
||||
name: 'js-yaml',
|
||||
location: __dirname + '/node_modules/js-yaml/dist',
|
||||
main: 'js-yaml'
|
||||
}, {
|
||||
name: 'vscode-json-languageservice',
|
||||
location: __dirname + '/node_modules/vscode-json-languageservice/lib',
|
||||
location: __dirname + '/node_modules/vscode-json-languageservice',
|
||||
main: 'jsonLanguageService'
|
||||
}, {
|
||||
name: 'vscode-languageserver-types',
|
||||
|
|
@ -82,6 +98,10 @@ gulp.task('release', ['clean-release','compile'], function() {
|
|||
name: 'vscode-nls',
|
||||
location: __dirname + '/out/fillers',
|
||||
main: 'vscode-nls'
|
||||
}, {
|
||||
name: 'os',
|
||||
location: __dirname + '/out/fillers',
|
||||
main: 'os'
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,14 @@
|
|||
"gulp-requirejs": "^0.1.3",
|
||||
"gulp-tsb": "^2.0.0",
|
||||
"gulp-uglify": "^1.5.3",
|
||||
"jsonc-parser": "1.0.0",
|
||||
"merge-stream": "^1.0.0",
|
||||
"monaco-editor-core": "^0.10.1",
|
||||
"monaco-languages": "^0.9.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"rimraf": "^2.5.2",
|
||||
"typescript": "^2.7.1",
|
||||
"vscode-languageserver-types": "^3.5.0",
|
||||
"vscode-json-languageservice": "^3.0.5"
|
||||
"vscode-json-languageservice": "^3.0.5",
|
||||
"vscode-languageserver-types": "^3.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
src/fillers/os.ts
Normal file
1
src/fillers/os.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const EOL = '\n';
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
"use strict"
|
||||
|
||||
export function insertionPointReturnValue(pt: number) {
|
||||
return ((-pt) - 1)
|
||||
}
|
||||
|
||||
export function binarySearch(array: number[], sought: number) {
|
||||
|
||||
let lower = 0
|
||||
let upper = array.length - 1
|
||||
|
||||
while (lower <= upper) {
|
||||
let idx = Math.floor((lower + upper) / 2)
|
||||
const value = array[idx]
|
||||
|
||||
if (value === sought) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if (lower === upper) {
|
||||
const insertionPoint = (value < sought) ? idx + 1 : idx
|
||||
return insertionPointReturnValue(insertionPoint)
|
||||
}
|
||||
|
||||
if (sought > value) {
|
||||
lower = idx + 1;
|
||||
} else if (sought < value) {
|
||||
upper = idx - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getLineStartPositions(text: string) {
|
||||
const lineStartPositions = [0];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
|
||||
if (c === '\r') {
|
||||
// Check for Windows encoding, otherwise we are old Mac
|
||||
if (i + 1 < text.length && text[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
|
||||
lineStartPositions.push(i + 1);
|
||||
} else if (c === '\n'){
|
||||
lineStartPositions.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return lineStartPositions;
|
||||
}
|
||||
|
||||
export function getPosition(pos: number, lineStartPositions: number[]){
|
||||
let line = binarySearch(lineStartPositions, pos)
|
||||
|
||||
if (line < 0){
|
||||
const insertionPoint = -1 * line - 1;
|
||||
line = insertionPoint - 1;
|
||||
}
|
||||
|
||||
return {line, column: pos - lineStartPositions[line]}
|
||||
}
|
||||
326
src/vscode-yaml-languageservice/parser/yamlParser.ts
Normal file
326
src/vscode-yaml-languageservice/parser/yamlParser.ts
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
'use strict';
|
||||
|
||||
import { JSONDocument, ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IError, IApplicableSchema } from 'vscode-json-languageservice/lib/parser/jsonParser';
|
||||
import { JSONSchema } from 'vscode-json-languageservice/lib/jsonSchema';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as Yaml from '../../yaml-ast-parser/index'
|
||||
import { Kind } from '../../yaml-ast-parser/index'
|
||||
|
||||
import { getLineStartPositions, getPosition } from '../documentPositionCalculator'
|
||||
|
||||
export class SingleYAMLDocument extends JSONDocument {
|
||||
private lines;
|
||||
public root;
|
||||
|
||||
constructor(lines: number[]) {
|
||||
super({ disallowComments: false, ignoreDanglingComma: true });
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
// TODO: This is complicated, messy and probably buggy
|
||||
// It should be re-written.
|
||||
// To get the correct behavior, it probably needs to be aware of
|
||||
// the type of the nodes it is processing since there are no delimiters
|
||||
// like in JSON. (ie. so it correctly returns 'object' vs 'property')
|
||||
public getNodeFromOffsetEndInclusive(offset: number): ASTNode {
|
||||
if (!this.root) {
|
||||
return;
|
||||
}
|
||||
if (offset < this.root.start || offset > this.root.end) {
|
||||
// We somehow are completely outside the document
|
||||
// This is unexpected
|
||||
console.log("Attempting to resolve node outside of document")
|
||||
return null;
|
||||
}
|
||||
|
||||
const children = this.root.getChildNodes()
|
||||
|
||||
function* sliding2(nodes: ASTNode[]) {
|
||||
var i = 0;
|
||||
while (i < nodes.length) {
|
||||
yield [nodes[i], (i === nodes.length) ? null : nodes[i + 1]]
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const onLaterLine = (offset: number, node: ASTNode) => {
|
||||
const { line: actualLine } = getPosition(offset, this.lines)
|
||||
const { line: nodeEndLine } = getPosition(node.end, this.lines)
|
||||
|
||||
return actualLine > nodeEndLine;
|
||||
}
|
||||
|
||||
let findNode = (nodes: ASTNode[]): ASTNode => {
|
||||
if (nodes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var gen = sliding2(nodes);
|
||||
|
||||
let result: IteratorResult<ASTNode[]> = { done: false, value: undefined }
|
||||
|
||||
for (let [first, second] of gen) {
|
||||
const end = (second) ? second.start : first.parent.end
|
||||
if (offset >= first.start && offset < end) {
|
||||
const children = first.getChildNodes();
|
||||
|
||||
const foundChild = findNode(children)
|
||||
|
||||
if (foundChild) {
|
||||
if (foundChild['isKey'] && foundChild.end < offset) {
|
||||
return foundChild.parent;
|
||||
}
|
||||
|
||||
if (foundChild.type === "null") {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundChild && onLaterLine(offset, first)) {
|
||||
return this.getNodeByIndent(this.lines, offset, this.root)
|
||||
}
|
||||
|
||||
return foundChild || first;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return findNode(children) || this.root;
|
||||
}
|
||||
|
||||
public getNodeFromOffset(offset: number): ASTNode {
|
||||
return this.getNodeFromOffsetEndInclusive(offset);
|
||||
}
|
||||
|
||||
private getNodeByIndent = (lines: number[], offset: number, node: ASTNode) => {
|
||||
|
||||
const { line, column: indent } = getPosition(offset, this.lines)
|
||||
|
||||
const children = node.getChildNodes()
|
||||
|
||||
function findNode(children) {
|
||||
for (var idx = 0; idx < children.length; idx++) {
|
||||
var child = children[idx];
|
||||
|
||||
const { line: childLine, column: childCol } = getPosition(child.start, lines);
|
||||
|
||||
if (childCol > indent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newChildren = child.getChildNodes()
|
||||
const foundNode = findNode(newChildren)
|
||||
|
||||
if (foundNode) {
|
||||
return foundNode;
|
||||
}
|
||||
|
||||
// We have the right indentation, need to return based on line
|
||||
if (childLine == line) {
|
||||
return child;
|
||||
}
|
||||
if (childLine > line) {
|
||||
// Get previous
|
||||
(idx - 1) >= 0 ? children[idx - 1] : child;
|
||||
}
|
||||
// Else continue loop to try next element
|
||||
}
|
||||
|
||||
// Special case, we found the correct
|
||||
return children[children.length - 1]
|
||||
}
|
||||
|
||||
return findNode(children) || node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case Yaml.Kind.MAP: {
|
||||
const instance = <Yaml.YamlMap>node;
|
||||
|
||||
const result = new ObjectASTNode(parent, null, node.startPosition, node.endPosition)
|
||||
result.addProperty
|
||||
|
||||
for (const mapping of instance.mappings) {
|
||||
result.addProperty(<PropertyASTNode>recursivelyBuildAst(result, mapping))
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.MAPPING: {
|
||||
const instance = <Yaml.YAMLMapping>node;
|
||||
const key = instance.key;
|
||||
|
||||
// Technically, this is an arbitrary node in YAML
|
||||
// I doubt we would get a better string representation by parsing it
|
||||
const keyNode = new StringASTNode(null, null, true, key.startPosition, key.endPosition);
|
||||
keyNode.value = key.value;
|
||||
|
||||
const result = new PropertyASTNode(parent, keyNode)
|
||||
result.end = instance.endPosition
|
||||
|
||||
const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNode(parent, key.value, instance.endPosition, instance.endPosition)
|
||||
valueNode.location = key.value
|
||||
|
||||
result.setValue(valueNode)
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.SEQ: {
|
||||
const instance = <Yaml.YAMLSequence>node;
|
||||
|
||||
const result = new ArrayASTNode(parent, null, instance.startPosition, instance.endPosition);
|
||||
|
||||
let count = 0;
|
||||
for (const item of instance.items) {
|
||||
if (item === null && count === instance.items.length - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Be aware of https://github.com/nodeca/js-yaml/issues/321
|
||||
// Cannot simply work around it here because we need to know if we are in Flow or Block
|
||||
var itemNode = (item === null) ? new NullASTNode(parent, null, instance.endPosition, instance.endPosition) : recursivelyBuildAst(result, item);
|
||||
|
||||
itemNode.location = count++;
|
||||
result.addItem(itemNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.SCALAR: {
|
||||
const instance = <Yaml.YAMLScalar>node;
|
||||
|
||||
const type = Yaml.determineScalarType(instance)
|
||||
|
||||
// The name is set either by the sequence or the mapping case.
|
||||
const name = null;
|
||||
const value = instance.value;
|
||||
|
||||
switch (type) {
|
||||
case Yaml.ScalarType.null: {
|
||||
return new NullASTNode(parent, name, instance.startPosition, instance.endPosition);
|
||||
}
|
||||
case Yaml.ScalarType.bool: {
|
||||
return new BooleanASTNode(parent, name, Yaml.parseYamlBoolean(value), node.startPosition, node.endPosition)
|
||||
}
|
||||
case Yaml.ScalarType.int: {
|
||||
const result = new NumberASTNode(parent, name, node.startPosition, node.endPosition);
|
||||
result.value = Yaml.parseYamlInteger(value);
|
||||
result.isInteger = true;
|
||||
return result;
|
||||
}
|
||||
case Yaml.ScalarType.float: {
|
||||
const result = new NumberASTNode(parent, name, node.startPosition, node.endPosition);
|
||||
result.value = Yaml.parseYamlFloat(value);
|
||||
result.isInteger = false;
|
||||
return result;
|
||||
}
|
||||
case Yaml.ScalarType.string: {
|
||||
const result = new StringASTNode(parent, name, false, node.startPosition, node.endPosition);
|
||||
result.value = node.value;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Yaml.Kind.ANCHOR_REF: {
|
||||
const instance = (<Yaml.YAMLAnchorReference>node).value
|
||||
|
||||
return recursivelyBuildAst(parent, instance) ||
|
||||
new NullASTNode(parent, null, node.startPosition, node.endPosition);
|
||||
}
|
||||
case Yaml.Kind.INCLUDE_REF: {
|
||||
// Issue Warning
|
||||
console.log("Unsupported feature, node kind: " + node.kind);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertError(e: Yaml.YAMLException) {
|
||||
// Subtract 2 because \n\0 is added by the parser (see loader.ts/loadDocuments)
|
||||
const bufferLength = e.mark.buffer.length - 2;
|
||||
|
||||
// TODO determine correct positioning.
|
||||
return { message: `${e.message}`, location: { start: Math.min(e.mark.position, bufferLength - 1), end: bufferLength, code: ErrorCode.Undefined } }
|
||||
}
|
||||
|
||||
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) {
|
||||
let _doc = new SingleYAMLDocument(startPositions);
|
||||
_doc.root = recursivelyBuildAst(null, yamlDoc)
|
||||
|
||||
if (!_doc.root) {
|
||||
// TODO: When this is true, consider not pushing the other errors.
|
||||
_doc.errors.push({ message: localize('Invalid symbol', 'Expected a YAML object, array or literal'), code: ErrorCode.Undefined, location: { start: yamlDoc.startPosition, end: yamlDoc.endPosition } });
|
||||
}
|
||||
|
||||
const duplicateKeyReason = 'duplicate key'
|
||||
|
||||
const errors = yamlDoc.errors.filter(e => e.reason !== duplicateKeyReason && !e.isWarning).map(e => convertError(e))
|
||||
const warnings = yamlDoc.errors.filter(e => e.reason === duplicateKeyReason || e.isWarning).map(e => convertError(e))
|
||||
|
||||
errors.forEach(e => _doc.errors.push(e));
|
||||
warnings.forEach(e => _doc.warnings.push(e));
|
||||
|
||||
return _doc;
|
||||
}
|
||||
|
||||
export class YAMLDocument {
|
||||
public documents: JSONDocument[]
|
||||
|
||||
constructor(documents: JSONDocument[]) {
|
||||
this.documents = documents;
|
||||
}
|
||||
|
||||
get errors(): IError[] {
|
||||
return (<IError[]>[]).concat(...this.documents.map(d => d.errors))
|
||||
}
|
||||
|
||||
get warnings(): IError[] {
|
||||
return (<IError[]>[]).concat(...this.documents.map(d => d.warnings))
|
||||
}
|
||||
|
||||
public getNodeFromOffsetEndInclusive(offset: number): ASTNode {
|
||||
return this.getNodeFromOffset(offset);
|
||||
}
|
||||
public getNodeFromOffset(offset: number): ASTNode {
|
||||
// Depends on the documents being sorted
|
||||
for (let element of this.documents) {
|
||||
if (offset <= element.root.end) {
|
||||
return element.getNodeFromOffset(offset)
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public validate(schema: JSONSchema, matchingSchemas: IApplicableSchema[] = null, offset: number = -1): void {
|
||||
this.documents.forEach(doc => {
|
||||
doc.validate(schema, matchingSchemas, offset)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function parse(text: string): YAMLDocument {
|
||||
|
||||
const startPositions = getLineStartPositions(text)
|
||||
// This is documented to return a YAMLNode even though the
|
||||
// typing only returns a YAMLDocument
|
||||
const yamlDocs = []
|
||||
Yaml.loadAll(text, doc => yamlDocs.push(doc), {})
|
||||
|
||||
return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions)));
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
"use strict"
|
||||
|
||||
import { TextDocument } from "vscode-languageserver-types";
|
||||
|
||||
export function isInComment(document: TextDocument, start: number, offset: number) {
|
||||
const text = document.getText().substr(start, offset);
|
||||
return /(?:^|\s+)#/.test(text);
|
||||
}
|
||||
26
src/vscode-yaml-languageservice/services/yamlFormatter.ts
Normal file
26
src/vscode-yaml-languageservice/services/yamlFormatter.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
import jsyaml = require('js-yaml')
|
||||
import { EOL } from 'os';
|
||||
import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types';
|
||||
|
||||
export function format(document: TextDocument, options: FormattingOptions): TextEdit[] {
|
||||
const text = document.getText()
|
||||
|
||||
const documents = []
|
||||
jsyaml.loadAll(text, doc => documents.push(doc))
|
||||
|
||||
const dumpOptions = { indent: options.tabSize, noCompatMode: true };
|
||||
|
||||
let newText;
|
||||
if (documents.length == 1) {
|
||||
const yaml = documents[0]
|
||||
newText = jsyaml.safeDump(yaml, dumpOptions)
|
||||
}
|
||||
else {
|
||||
const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions))
|
||||
newText = '%YAML 1.2' + EOL + '---' + EOL + formatted.join('...' + EOL + '---' + EOL) + '...' + EOL
|
||||
}
|
||||
|
||||
return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(text.length)), newText)]
|
||||
}
|
||||
19
src/vscode-yaml-languageservice/tsconfig.json
Normal file
19
src/vscode-yaml-languageservice/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "umd",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"outDir": "../lib",
|
||||
"lib": [
|
||||
"es6"
|
||||
],
|
||||
"baseUrl": "../types",
|
||||
"typeRoots": [
|
||||
"../node_modules/@types",
|
||||
"../types"
|
||||
]
|
||||
}
|
||||
}
|
||||
187
src/vscode-yaml-languageservice/yamlLanguageService.ts
Normal file
187
src/vscode-yaml-languageservice/yamlLanguageService.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Adam Voss. All rights reserved.
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
|
||||
TextEdit, FormattingOptions, MarkedString} from 'vscode-languageserver-types';
|
||||
|
||||
import {JSONCompletion} from 'vscode-json-languageservice/lib/services/jsonCompletion';
|
||||
import {JSONHover} from 'vscode-json-languageservice/lib/services/jsonHover';
|
||||
import {JSONValidation} from 'vscode-json-languageservice/lib/services/jsonValidation';
|
||||
import {JSONSchema} from 'vscode-json-languageservice/lib/jsonSchema';
|
||||
import {JSONDocumentSymbols} from 'vscode-json-languageservice/lib/services/jsonDocumentSymbols';
|
||||
import {parse as JSONDocumentConfig} from 'vscode-json-languageservice/lib/parser/jsonParser';
|
||||
|
||||
import {parse as parseYAML} from './parser/yamlParser';
|
||||
import {isInComment} from './services/yamlCompletion'
|
||||
import {format as formatYAML} from './services/yamlFormatter';
|
||||
|
||||
import {schemaContributions} from 'vscode-json-languageservice/lib/services/configuration';
|
||||
import {JSONSchemaService} from 'vscode-json-languageservice/lib/services/jsonSchemaService';
|
||||
import {JSONWorkerContribution, JSONPath, Segment, CompletionsCollector} from 'vscode-json-languageservice/lib/jsonContributions';
|
||||
|
||||
export type JSONDocument = {}
|
||||
export type YAMLDocument = { documents: JSONDocument[]}
|
||||
export {JSONSchema, JSONWorkerContribution, JSONPath, Segment, CompletionsCollector};
|
||||
export {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
|
||||
TextEdit, FormattingOptions, MarkedString};
|
||||
|
||||
export interface LanguageService {
|
||||
configure(settings: LanguageSettings): void;
|
||||
doValidation(document: TextDocument, yamlDocument: YAMLDocument): Thenable<Diagnostic[]>;
|
||||
parseYAMLDocument(document: TextDocument): YAMLDocument;
|
||||
resetSchema(uri: string): boolean;
|
||||
doResolve(item: CompletionItem): Thenable<CompletionItem>;
|
||||
doComplete(document: TextDocument, position: Position, doc: YAMLDocument): Thenable<CompletionList>;
|
||||
findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[];
|
||||
doHover(document: TextDocument, position: Position, doc: YAMLDocument): Thenable<Hover>;
|
||||
format(document: TextDocument, options: FormattingOptions): TextEdit[];
|
||||
}
|
||||
|
||||
export interface LanguageSettings {
|
||||
/**
|
||||
* If set, the validator will return syntax errors.
|
||||
*/
|
||||
validate?: boolean;
|
||||
|
||||
/**
|
||||
* A list of known schemas and/or associations of schemas to file names.
|
||||
*/
|
||||
schemas?: SchemaConfiguration[];
|
||||
}
|
||||
|
||||
export interface SchemaConfiguration {
|
||||
/**
|
||||
* The URI of the schema, which is also the identifier of the schema.
|
||||
*/
|
||||
uri: string;
|
||||
/**
|
||||
* A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json'
|
||||
*/
|
||||
fileMatch?: string[];
|
||||
/**
|
||||
* The schema for the given URI.
|
||||
* If no schema is provided, the schema will be fetched with the schema request service (if available).
|
||||
*/
|
||||
schema?: JSONSchema;
|
||||
}
|
||||
|
||||
export interface WorkspaceContextService {
|
||||
resolveRelativePath(relativePath: string, resource: string): string;
|
||||
}
|
||||
/**
|
||||
* The schema request service is used to fetch schemas. The result should the schema file comment, or,
|
||||
* in case of an error, a displayable error string
|
||||
*/
|
||||
export interface SchemaRequestService {
|
||||
(uri: string): Thenable<string>;
|
||||
}
|
||||
|
||||
export interface PromiseConstructor {
|
||||
/**
|
||||
* Creates a new Promise.
|
||||
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
|
||||
* a resolve callback used resolve the promise with a value or the result of another promise,
|
||||
* and a reject callback used to reject the promise with a provided reason or error.
|
||||
*/
|
||||
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Thenable<T>;
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved with an array of results when all of the provided Promises
|
||||
* resolve, or rejected when any Promise is rejected.
|
||||
* @param values An array of Promises.
|
||||
* @returns A new Promise.
|
||||
*/
|
||||
all<T>(values: Array<T | Thenable<T>>): Thenable<T[]>;
|
||||
/**
|
||||
* Creates a new rejected promise for the provided reason.
|
||||
* @param reason The reason the promise was rejected.
|
||||
* @returns A new rejected Promise.
|
||||
*/
|
||||
reject<T>(reason: any): Thenable<T>;
|
||||
|
||||
/**
|
||||
* Creates a new resolved promise for the provided value.
|
||||
* @param value A promise.
|
||||
* @returns A promise whose internal state matches the provided promise.
|
||||
*/
|
||||
resolve<T>(value: T | Thenable<T>): Thenable<T>;
|
||||
|
||||
}
|
||||
|
||||
export interface Thenable<R> {
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
}
|
||||
|
||||
export interface LanguageServiceParams {
|
||||
/**
|
||||
* The schema request service is used to fetch schemas. The result should the schema file comment, or,
|
||||
* in case of an error, a displayable error string
|
||||
*/
|
||||
schemaRequestService?: SchemaRequestService;
|
||||
/**
|
||||
* The workspace context is used to resolve relative paths for relative schema references.
|
||||
*/
|
||||
workspaceContext?: WorkspaceContextService;
|
||||
/**
|
||||
* An optional set of completion and hover participants.
|
||||
*/
|
||||
contributions?: JSONWorkerContribution[];
|
||||
/**
|
||||
* A promise constructor. If not set, the ES5 Promise will be used.
|
||||
*/
|
||||
promiseConstructor?: PromiseConstructor;
|
||||
}
|
||||
|
||||
export function getLanguageService(params: LanguageServiceParams): LanguageService {
|
||||
let promise = params.promiseConstructor || Promise;
|
||||
|
||||
let jsonSchemaService = new JSONSchemaService(params.schemaRequestService, params.workspaceContext, promise);
|
||||
jsonSchemaService.setSchemaContributions(schemaContributions);
|
||||
|
||||
let jsonCompletion = new JSONCompletion(jsonSchemaService, params.contributions, promise);
|
||||
jsonCompletion['isInComment'] = isInComment.bind(jsonCompletion);
|
||||
|
||||
let jsonHover = new JSONHover(jsonSchemaService, params.contributions, promise);
|
||||
let jsonDocumentSymbols = new JSONDocumentSymbols(jsonSchemaService);
|
||||
let jsonValidation = new JSONValidation(jsonSchemaService, promise);
|
||||
|
||||
|
||||
function doValidation(textDocument: TextDocument, yamlDocument: YAMLDocument) {
|
||||
var validate: (JSONDocument) => Thenable<Diagnostic[]> =
|
||||
jsonValidation.doValidation.bind(jsonValidation, textDocument)
|
||||
const validationResults = yamlDocument.documents.map(d => validate(d))
|
||||
const resultsPromise = promise.all(validationResults);
|
||||
return resultsPromise.then(res => (<Diagnostic[]>[]).concat(...res))
|
||||
}
|
||||
|
||||
return {
|
||||
configure: (settings: LanguageSettings) => {
|
||||
jsonSchemaService.clearExternalSchemas();
|
||||
if (settings.schemas) {
|
||||
settings.schemas.forEach(settings => {
|
||||
jsonSchemaService.registerExternalSchema(settings.uri, settings.fileMatch, settings.schema);
|
||||
});
|
||||
};
|
||||
jsonValidation.configure(settings);
|
||||
},
|
||||
resetSchema: (uri: string) => jsonSchemaService.onResourceChange(uri),
|
||||
doValidation: doValidation,
|
||||
parseYAMLDocument : (document: TextDocument) => parseYAML(document.getText()),
|
||||
doResolve: jsonCompletion.doResolve.bind(jsonCompletion),
|
||||
doComplete: jsonCompletion.doComplete.bind(jsonCompletion),
|
||||
findDocumentSymbols: jsonDocumentSymbols.findDocumentSymbols.bind(jsonDocumentSymbols),
|
||||
doHover: jsonHover.doHover.bind(jsonHover),
|
||||
format: formatYAML
|
||||
};
|
||||
}
|
||||
52
src/yaml-ast-parser/common.ts
Normal file
52
src/yaml-ast-parser/common.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
|
||||
export function isNothing(subject) {
|
||||
return (typeof subject === 'undefined') || (null === subject);
|
||||
}
|
||||
|
||||
|
||||
export function isObject(subject) {
|
||||
return (typeof subject === 'object') && (null !== subject);
|
||||
}
|
||||
|
||||
|
||||
export function toArray(sequence) {
|
||||
if (Array.isArray(sequence)) {
|
||||
return sequence;
|
||||
} else if (isNothing(sequence)) {
|
||||
return [];
|
||||
}
|
||||
return [sequence];
|
||||
}
|
||||
|
||||
|
||||
export function extend(target, source) {
|
||||
var index, length, key, sourceKeys;
|
||||
|
||||
if (source) {
|
||||
sourceKeys = Object.keys(source);
|
||||
|
||||
for (index = 0, length = sourceKeys.length; index < length; index += 1) {
|
||||
key = sourceKeys[index];
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
export function repeat(string, count) {
|
||||
var result = '', cycle;
|
||||
|
||||
for (cycle = 0; cycle < count; cycle += 1) {
|
||||
result += string;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function isNegativeZero(number) {
|
||||
return (0 === number) && (Number.NEGATIVE_INFINITY === 1 / number);
|
||||
}
|
||||
831
src/yaml-ast-parser/dumper.ts
Normal file
831
src/yaml-ast-parser/dumper.ts
Normal file
|
|
@ -0,0 +1,831 @@
|
|||
/*eslint-disable no-use-before-define*/
|
||||
|
||||
import * as common from './common';
|
||||
import YAMLException from './exception';
|
||||
import DEFAULT_FULL_SCHEMA from './schema/default_full';
|
||||
import DEFAULT_SAFE_SCHEMA from './schema/default_safe';
|
||||
|
||||
var _toString = Object.prototype.toString;
|
||||
var _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
var CHAR_TAB = 0x09; /* Tab */
|
||||
var CHAR_LINE_FEED = 0x0A; /* LF */
|
||||
var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */
|
||||
var CHAR_SPACE = 0x20; /* Space */
|
||||
var CHAR_EXCLAMATION = 0x21; /* ! */
|
||||
var CHAR_DOUBLE_QUOTE = 0x22; /* " */
|
||||
var CHAR_SHARP = 0x23; /* # */
|
||||
var CHAR_PERCENT = 0x25; /* % */
|
||||
var CHAR_AMPERSAND = 0x26; /* & */
|
||||
var CHAR_SINGLE_QUOTE = 0x27; /* ' */
|
||||
var CHAR_ASTERISK = 0x2A; /* * */
|
||||
var CHAR_COMMA = 0x2C; /* , */
|
||||
var CHAR_MINUS = 0x2D; /* - */
|
||||
var CHAR_COLON = 0x3A; /* : */
|
||||
var CHAR_GREATER_THAN = 0x3E; /* > */
|
||||
var CHAR_QUESTION = 0x3F; /* ? */
|
||||
var CHAR_COMMERCIAL_AT = 0x40; /* @ */
|
||||
var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */
|
||||
var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
|
||||
var CHAR_GRAVE_ACCENT = 0x60; /* ` */
|
||||
var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */
|
||||
var CHAR_VERTICAL_LINE = 0x7C; /* | */
|
||||
var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */
|
||||
|
||||
var ESCAPE_SEQUENCES = {};
|
||||
|
||||
ESCAPE_SEQUENCES[0x00] = '\\0';
|
||||
ESCAPE_SEQUENCES[0x07] = '\\a';
|
||||
ESCAPE_SEQUENCES[0x08] = '\\b';
|
||||
ESCAPE_SEQUENCES[0x09] = '\\t';
|
||||
ESCAPE_SEQUENCES[0x0A] = '\\n';
|
||||
ESCAPE_SEQUENCES[0x0B] = '\\v';
|
||||
ESCAPE_SEQUENCES[0x0C] = '\\f';
|
||||
ESCAPE_SEQUENCES[0x0D] = '\\r';
|
||||
ESCAPE_SEQUENCES[0x1B] = '\\e';
|
||||
ESCAPE_SEQUENCES[0x22] = '\\"';
|
||||
ESCAPE_SEQUENCES[0x5C] = '\\\\';
|
||||
ESCAPE_SEQUENCES[0x85] = '\\N';
|
||||
ESCAPE_SEQUENCES[0xA0] = '\\_';
|
||||
ESCAPE_SEQUENCES[0x2028] = '\\L';
|
||||
ESCAPE_SEQUENCES[0x2029] = '\\P';
|
||||
|
||||
var DEPRECATED_BOOLEANS_SYNTAX = [
|
||||
'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',
|
||||
'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'
|
||||
];
|
||||
|
||||
function compileStyleMap(schema, map) {
|
||||
var result, keys, index, length, tag, style, type;
|
||||
|
||||
if (null === map) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result = {};
|
||||
keys = Object.keys(map);
|
||||
|
||||
for (index = 0, length = keys.length; index < length; index += 1) {
|
||||
tag = keys[index];
|
||||
style = String(map[tag]);
|
||||
|
||||
if ('!!' === tag.slice(0, 2)) {
|
||||
tag = 'tag:yaml.org,2002:' + tag.slice(2);
|
||||
}
|
||||
|
||||
type = schema.compiledTypeMap[tag];
|
||||
|
||||
if (type && _hasOwnProperty.call(type.styleAliases, style)) {
|
||||
style = type.styleAliases[style];
|
||||
}
|
||||
|
||||
result[tag] = style;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function encodeHex(character) {
|
||||
var string, handle, length;
|
||||
|
||||
string = character.toString(16).toUpperCase();
|
||||
|
||||
if (character <= 0xFF) {
|
||||
handle = 'x';
|
||||
length = 2;
|
||||
} else if (character <= 0xFFFF) {
|
||||
handle = 'u';
|
||||
length = 4;
|
||||
} else if (character <= 0xFFFFFFFF) {
|
||||
handle = 'U';
|
||||
length = 8;
|
||||
} else {
|
||||
throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');
|
||||
}
|
||||
|
||||
return '\\' + handle + common.repeat('0', length - string.length) + string;
|
||||
}
|
||||
|
||||
function State(options) {
|
||||
this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;
|
||||
this.indent = Math.max(1, (options['indent'] || 2));
|
||||
this.skipInvalid = options['skipInvalid'] || false;
|
||||
this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);
|
||||
this.styleMap = compileStyleMap(this.schema, options['styles'] || null);
|
||||
|
||||
this.implicitTypes = this.schema.compiledImplicit;
|
||||
this.explicitTypes = this.schema.compiledExplicit;
|
||||
|
||||
this.tag = null;
|
||||
this.result = '';
|
||||
|
||||
this.duplicates = [];
|
||||
this.usedDuplicates = null;
|
||||
}
|
||||
|
||||
function indentString(string: string, spaces) {
|
||||
var ind = common.repeat(' ', spaces),
|
||||
position = 0,
|
||||
next = -1,
|
||||
result = '',
|
||||
line,
|
||||
length = string.length;
|
||||
|
||||
while (position < length) {
|
||||
next = string.indexOf('\n', position);
|
||||
if (next === -1) {
|
||||
line = string.slice(position);
|
||||
position = length;
|
||||
} else {
|
||||
line = string.slice(position, next + 1);
|
||||
position = next + 1;
|
||||
}
|
||||
if (line.length && line !== '\n') {
|
||||
result += ind;
|
||||
}
|
||||
result += line;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function generateNextLine(state, level) {
|
||||
return '\n' + common.repeat(' ', state.indent * level);
|
||||
}
|
||||
|
||||
function testImplicitResolving(state, str) {
|
||||
var index, length, type;
|
||||
|
||||
for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
|
||||
type = state.implicitTypes[index];
|
||||
|
||||
if (type.resolve(str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function StringBuilder(source) {
|
||||
this.source = source;
|
||||
this.result = '';
|
||||
this.checkpoint = 0;
|
||||
}
|
||||
|
||||
StringBuilder.prototype.takeUpTo = function (position) {
|
||||
var er;
|
||||
|
||||
if (position < this.checkpoint) {
|
||||
er = new Error('position should be > checkpoint');
|
||||
er.position = position;
|
||||
er.checkpoint = this.checkpoint;
|
||||
throw er;
|
||||
}
|
||||
|
||||
this.result += this.source.slice(this.checkpoint, position);
|
||||
this.checkpoint = position;
|
||||
return this;
|
||||
};
|
||||
|
||||
StringBuilder.prototype.escapeChar = function () {
|
||||
var character, esc;
|
||||
|
||||
character = this.source.charCodeAt(this.checkpoint);
|
||||
esc = ESCAPE_SEQUENCES[character] || encodeHex(character);
|
||||
this.result += esc;
|
||||
this.checkpoint += 1;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
StringBuilder.prototype.finish = function () {
|
||||
if (this.source.length > this.checkpoint) {
|
||||
this.takeUpTo(this.source.length);
|
||||
}
|
||||
};
|
||||
|
||||
function writeScalar(state, object, level) {
|
||||
var simple, first, spaceWrap, folded, literal, single, double,
|
||||
sawLineFeed, linePosition, longestLine, indent, max, character,
|
||||
position, escapeSeq, hexEsc, previous, lineLength, modifier,
|
||||
trailingLineBreaks, result;
|
||||
|
||||
if (0 === object.length) {
|
||||
state.dump = "''";
|
||||
return;
|
||||
}
|
||||
if (object.indexOf("!include") == 0) {
|
||||
state.dump = "" + object;//FIXME
|
||||
return;
|
||||
}
|
||||
if (object.indexOf("!$$$novalue") == 0) {
|
||||
state.dump = "";//FIXME
|
||||
return;
|
||||
}
|
||||
if (-1 !== DEPRECATED_BOOLEANS_SYNTAX.indexOf(object)) {
|
||||
state.dump = "'" + object + "'";
|
||||
return;
|
||||
}
|
||||
|
||||
simple = true;
|
||||
first = object.length ? object.charCodeAt(0) : 0;
|
||||
spaceWrap = (CHAR_SPACE === first ||
|
||||
CHAR_SPACE === object.charCodeAt(object.length - 1));
|
||||
|
||||
// Simplified check for restricted first characters
|
||||
// http://www.yaml.org/spec/1.2/spec.html#ns-plain-first%28c%29
|
||||
if (CHAR_MINUS === first ||
|
||||
CHAR_QUESTION === first ||
|
||||
CHAR_COMMERCIAL_AT === first ||
|
||||
CHAR_GRAVE_ACCENT === first) {
|
||||
simple = false;
|
||||
}
|
||||
|
||||
// can only use > and | if not wrapped in spaces.
|
||||
if (spaceWrap) {
|
||||
simple = false;
|
||||
folded = false;
|
||||
literal = false;
|
||||
} else {
|
||||
folded = true;
|
||||
literal = true;
|
||||
}
|
||||
|
||||
single = true;
|
||||
double = new StringBuilder(object);
|
||||
|
||||
sawLineFeed = false;
|
||||
linePosition = 0;
|
||||
longestLine = 0;
|
||||
|
||||
indent = state.indent * level;
|
||||
max = 80;
|
||||
if (indent < 40) {
|
||||
max -= indent;
|
||||
} else {
|
||||
max = 40;
|
||||
}
|
||||
|
||||
for (position = 0; position < object.length; position++) {
|
||||
character = object.charCodeAt(position);
|
||||
if (simple) {
|
||||
// Characters that can never appear in the simple scalar
|
||||
if (!simpleChar(character)) {
|
||||
simple = false;
|
||||
} else {
|
||||
// Still simple. If we make it all the way through like
|
||||
// this, then we can just dump the string as-is.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (single && character === CHAR_SINGLE_QUOTE) {
|
||||
single = false;
|
||||
}
|
||||
|
||||
escapeSeq = ESCAPE_SEQUENCES[character];
|
||||
hexEsc = needsHexEscape(character);
|
||||
|
||||
if (!escapeSeq && !hexEsc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (character !== CHAR_LINE_FEED &&
|
||||
character !== CHAR_DOUBLE_QUOTE &&
|
||||
character !== CHAR_SINGLE_QUOTE) {
|
||||
folded = false;
|
||||
literal = false;
|
||||
} else if (character === CHAR_LINE_FEED) {
|
||||
sawLineFeed = true;
|
||||
single = false;
|
||||
if (position > 0) {
|
||||
previous = object.charCodeAt(position - 1);
|
||||
if (previous === CHAR_SPACE) {
|
||||
literal = false;
|
||||
folded = false;
|
||||
}
|
||||
}
|
||||
if (folded) {
|
||||
lineLength = position - linePosition;
|
||||
linePosition = position;
|
||||
if (lineLength > longestLine) {
|
||||
longestLine = lineLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character !== CHAR_DOUBLE_QUOTE) {
|
||||
single = false;
|
||||
}
|
||||
|
||||
double.takeUpTo(position);
|
||||
double.escapeChar();
|
||||
}
|
||||
|
||||
if (simple && testImplicitResolving(state, object)) {
|
||||
simple = false;
|
||||
}
|
||||
|
||||
modifier = '';
|
||||
if (folded || literal) {
|
||||
trailingLineBreaks = 0;
|
||||
if (object.charCodeAt(object.length - 1) === CHAR_LINE_FEED) {
|
||||
trailingLineBreaks += 1;
|
||||
if (object.charCodeAt(object.length - 2) === CHAR_LINE_FEED) {
|
||||
trailingLineBreaks += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (trailingLineBreaks === 0) {
|
||||
modifier = '-';
|
||||
} else if (trailingLineBreaks === 2) {
|
||||
modifier = '+';
|
||||
}
|
||||
}
|
||||
|
||||
if (literal && longestLine < max) {
|
||||
folded = false;
|
||||
}
|
||||
|
||||
// If it's literally one line, then don't bother with the literal.
|
||||
// We may still want to do a fold, though, if it's a super long line.
|
||||
if (!sawLineFeed) {
|
||||
literal = false;
|
||||
}
|
||||
|
||||
if (simple) {
|
||||
state.dump = object;
|
||||
} else if (single) {
|
||||
state.dump = '\'' + object + '\'';
|
||||
} else if (folded) {
|
||||
result = fold(object, max);
|
||||
state.dump = '>' + modifier + '\n' + indentString(result, indent);
|
||||
} else if (literal) {
|
||||
if (!modifier) {
|
||||
object = object.replace(/\n$/, '');
|
||||
}
|
||||
state.dump = '|' + modifier + '\n' + indentString(object, indent);
|
||||
} else if (double) {
|
||||
double.finish();
|
||||
state.dump = '"' + double.result + '"';
|
||||
} else {
|
||||
throw new Error('Failed to dump scalar value');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// The `trailing` var is a regexp match of any trailing `\n` characters.
|
||||
//
|
||||
// There are three cases we care about:
|
||||
//
|
||||
// 1. One trailing `\n` on the string. Just use `|` or `>`.
|
||||
// This is the assumed default. (trailing = null)
|
||||
// 2. No trailing `\n` on the string. Use `|-` or `>-` to "chomp" the end.
|
||||
// 3. More than one trailing `\n` on the string. Use `|+` or `>+`.
|
||||
//
|
||||
// In the case of `>+`, these line breaks are *not* doubled (like the line
|
||||
// breaks within the string), so it's important to only end with the exact
|
||||
// same number as we started.
|
||||
function fold(object, max) {
|
||||
var result = '',
|
||||
position = 0,
|
||||
length = object.length,
|
||||
trailing = /\n+$/.exec(object),
|
||||
newLine;
|
||||
|
||||
if (trailing) {
|
||||
length = trailing.index + 1;
|
||||
}
|
||||
|
||||
while (position < length) {
|
||||
newLine = object.indexOf('\n', position);
|
||||
if (newLine > length || newLine === -1) {
|
||||
if (result) {
|
||||
result += '\n\n';
|
||||
}
|
||||
result += foldLine(object.slice(position, length), max);
|
||||
position = length;
|
||||
} else {
|
||||
if (result) {
|
||||
result += '\n\n';
|
||||
}
|
||||
result += foldLine(object.slice(position, newLine), max);
|
||||
position = newLine + 1;
|
||||
}
|
||||
}
|
||||
if (trailing && trailing[0] !== '\n') {
|
||||
result += trailing[0];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function foldLine(line, max) {
|
||||
if (line === '') {
|
||||
return line;
|
||||
}
|
||||
|
||||
var foldRe = /[^\s] [^\s]/g,
|
||||
result = '',
|
||||
prevMatch = 0,
|
||||
foldStart = 0,
|
||||
match = foldRe.exec(line),
|
||||
index,
|
||||
foldEnd,
|
||||
folded;
|
||||
|
||||
while (match) {
|
||||
index = match.index;
|
||||
|
||||
// when we cross the max len, if the previous match would've
|
||||
// been ok, use that one, and carry on. If there was no previous
|
||||
// match on this fold section, then just have a long line.
|
||||
if (index - foldStart > max) {
|
||||
if (prevMatch !== foldStart) {
|
||||
foldEnd = prevMatch;
|
||||
} else {
|
||||
foldEnd = index;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
result += '\n';
|
||||
}
|
||||
folded = line.slice(foldStart, foldEnd);
|
||||
result += folded;
|
||||
foldStart = foldEnd + 1;
|
||||
}
|
||||
prevMatch = index + 1;
|
||||
match = foldRe.exec(line);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
// if we end up with one last word at the end, then the last bit might
|
||||
// be slightly bigger than we wanted, because we exited out of the loop.
|
||||
if (foldStart !== prevMatch && line.length - foldStart > max) {
|
||||
result += line.slice(foldStart, prevMatch) + '\n' +
|
||||
line.slice(prevMatch + 1);
|
||||
} else {
|
||||
result += line.slice(foldStart);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if character can be found in a simple scalar
|
||||
function simpleChar(character) {
|
||||
return CHAR_TAB !== character &&
|
||||
CHAR_LINE_FEED !== character &&
|
||||
CHAR_CARRIAGE_RETURN !== character &&
|
||||
CHAR_COMMA !== character &&
|
||||
CHAR_LEFT_SQUARE_BRACKET !== character &&
|
||||
CHAR_RIGHT_SQUARE_BRACKET !== character &&
|
||||
CHAR_LEFT_CURLY_BRACKET !== character &&
|
||||
CHAR_RIGHT_CURLY_BRACKET !== character &&
|
||||
CHAR_SHARP !== character &&
|
||||
CHAR_AMPERSAND !== character &&
|
||||
CHAR_ASTERISK !== character &&
|
||||
CHAR_EXCLAMATION !== character &&
|
||||
CHAR_VERTICAL_LINE !== character &&
|
||||
CHAR_GREATER_THAN !== character &&
|
||||
CHAR_SINGLE_QUOTE !== character &&
|
||||
CHAR_DOUBLE_QUOTE !== character &&
|
||||
CHAR_PERCENT !== character &&
|
||||
CHAR_COLON !== character &&
|
||||
!ESCAPE_SEQUENCES[character] &&
|
||||
!needsHexEscape(character);
|
||||
}
|
||||
|
||||
// Returns true if the character code needs to be escaped.
|
||||
function needsHexEscape(character) {
|
||||
return !((0x00020 <= character && character <= 0x00007E) ||
|
||||
(0x00085 === character) ||
|
||||
(0x000A0 <= character && character <= 0x00D7FF) ||
|
||||
(0x0E000 <= character && character <= 0x00FFFD) ||
|
||||
(0x10000 <= character && character <= 0x10FFFF));
|
||||
}
|
||||
|
||||
function writeFlowSequence(state, level, object) {
|
||||
var _result = '',
|
||||
_tag = state.tag,
|
||||
index,
|
||||
length;
|
||||
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
// Write only valid elements.
|
||||
if (writeNode(state, level, object[index], false, false)) {
|
||||
if (0 !== index) {
|
||||
_result += ', ';
|
||||
}
|
||||
_result += state.dump;
|
||||
}
|
||||
}
|
||||
|
||||
state.tag = _tag;
|
||||
state.dump = '[' + _result + ']';
|
||||
}
|
||||
|
||||
function writeBlockSequence(state, level, object, compact) {
|
||||
var _result = '',
|
||||
_tag = state.tag,
|
||||
index,
|
||||
length;
|
||||
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
// Write only valid elements.
|
||||
if (writeNode(state, level + 1, object[index], true, true)) {
|
||||
if (!compact || 0 !== index) {
|
||||
_result += generateNextLine(state, level);
|
||||
}
|
||||
_result += '- ' + state.dump;
|
||||
}
|
||||
}
|
||||
|
||||
state.tag = _tag;
|
||||
state.dump = _result || '[]'; // Empty sequence if no valid values.
|
||||
}
|
||||
|
||||
function writeFlowMapping(state, level, object) {
|
||||
var _result = '',
|
||||
_tag = state.tag,
|
||||
objectKeyList = Object.keys(object),
|
||||
index,
|
||||
length,
|
||||
objectKey,
|
||||
objectValue,
|
||||
pairBuffer;
|
||||
|
||||
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
|
||||
pairBuffer = '';
|
||||
|
||||
if (0 !== index) {
|
||||
pairBuffer += ', ';
|
||||
}
|
||||
|
||||
objectKey = objectKeyList[index];
|
||||
objectValue = object[objectKey];
|
||||
|
||||
if (!writeNode(state, level, objectKey, false, false)) {
|
||||
continue; // Skip this pair because of invalid key;
|
||||
}
|
||||
|
||||
if (state.dump.length > 1024) {
|
||||
pairBuffer += '? ';
|
||||
}
|
||||
|
||||
pairBuffer += state.dump + ': ';
|
||||
|
||||
if (!writeNode(state, level, objectValue, false, false)) {
|
||||
continue; // Skip this pair because of invalid value.
|
||||
}
|
||||
|
||||
pairBuffer += state.dump;
|
||||
|
||||
// Both key and value are valid.
|
||||
_result += pairBuffer;
|
||||
}
|
||||
|
||||
state.tag = _tag;
|
||||
state.dump = '{' + _result + '}';
|
||||
}
|
||||
|
||||
function writeBlockMapping(state, level, object, compact) {
|
||||
var _result = '',
|
||||
_tag = state.tag,
|
||||
objectKeyList = Object.keys(object),
|
||||
index,
|
||||
length,
|
||||
objectKey,
|
||||
objectValue,
|
||||
explicitPair,
|
||||
pairBuffer;
|
||||
|
||||
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
|
||||
pairBuffer = '';
|
||||
|
||||
if (!compact || 0 !== index) {
|
||||
pairBuffer += generateNextLine(state, level);
|
||||
}
|
||||
|
||||
objectKey = objectKeyList[index];
|
||||
objectValue = object[objectKey];
|
||||
|
||||
if (!writeNode(state, level + 1, objectKey, true, true)) {
|
||||
continue; // Skip this pair because of invalid key.
|
||||
}
|
||||
|
||||
explicitPair = (null !== state.tag && '?' !== state.tag) ||
|
||||
(state.dump && state.dump.length > 1024);
|
||||
|
||||
if (explicitPair) {
|
||||
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
|
||||
pairBuffer += '?';
|
||||
} else {
|
||||
pairBuffer += '? ';
|
||||
}
|
||||
}
|
||||
|
||||
pairBuffer += state.dump;
|
||||
|
||||
if (explicitPair) {
|
||||
pairBuffer += generateNextLine(state, level);
|
||||
}
|
||||
|
||||
if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
|
||||
continue; // Skip this pair because of invalid value.
|
||||
}
|
||||
|
||||
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
|
||||
pairBuffer += ':';
|
||||
} else {
|
||||
pairBuffer += ': ';
|
||||
}
|
||||
|
||||
pairBuffer += state.dump;
|
||||
|
||||
// Both key and value are valid.
|
||||
_result += pairBuffer;
|
||||
}
|
||||
|
||||
state.tag = _tag;
|
||||
state.dump = _result || '{}'; // Empty mapping if no valid pairs.
|
||||
}
|
||||
|
||||
function detectType(state, object, explicit) {
|
||||
var _result, typeList, index, length, type, style;
|
||||
|
||||
typeList = explicit ? state.explicitTypes : state.implicitTypes;
|
||||
|
||||
for (index = 0, length = typeList.length; index < length; index += 1) {
|
||||
type = typeList[index];
|
||||
|
||||
if ((type.instanceOf || type.predicate) &&
|
||||
(!type.instanceOf || (('object' === typeof object) && (object instanceof type.instanceOf))) &&
|
||||
(!type.predicate || type.predicate(object))) {
|
||||
|
||||
state.tag = explicit ? type.tag : '?';
|
||||
|
||||
if (type.represent) {
|
||||
style = state.styleMap[type.tag] || type.defaultStyle;
|
||||
|
||||
if ('[object Function]' === _toString.call(type.represent)) {
|
||||
_result = type.represent(object, style);
|
||||
} else if (_hasOwnProperty.call(type.represent, style)) {
|
||||
_result = type.represent[style](object, style);
|
||||
} else {
|
||||
throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style');
|
||||
}
|
||||
|
||||
state.dump = _result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serializes `object` and writes it to global `result`.
|
||||
// Returns true on success, or false on invalid object.
|
||||
//
|
||||
function writeNode(state, level, object, block, compact) {
|
||||
state.tag = null;
|
||||
state.dump = object;
|
||||
|
||||
if (!detectType(state, object, false)) {
|
||||
detectType(state, object, true);
|
||||
}
|
||||
|
||||
var type = _toString.call(state.dump);
|
||||
|
||||
if (block) {
|
||||
block = (0 > state.flowLevel || state.flowLevel > level);
|
||||
}
|
||||
|
||||
if ((null !== state.tag && '?' !== state.tag) || (2 !== state.indent && level > 0)) {
|
||||
compact = false;
|
||||
}
|
||||
|
||||
var objectOrArray = '[object Object]' === type || '[object Array]' === type,
|
||||
duplicateIndex,
|
||||
duplicate;
|
||||
|
||||
if (objectOrArray) {
|
||||
duplicateIndex = state.duplicates.indexOf(object);
|
||||
duplicate = duplicateIndex !== -1;
|
||||
}
|
||||
|
||||
if (duplicate && state.usedDuplicates[duplicateIndex]) {
|
||||
state.dump = '*ref_' + duplicateIndex;
|
||||
} else {
|
||||
if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
|
||||
state.usedDuplicates[duplicateIndex] = true;
|
||||
}
|
||||
if ('[object Object]' === type) {
|
||||
if (block && (0 !== Object.keys(state.dump).length)) {
|
||||
writeBlockMapping(state, level, state.dump, compact);
|
||||
if (duplicate) {
|
||||
state.dump = '&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
|
||||
}
|
||||
} else {
|
||||
writeFlowMapping(state, level, state.dump);
|
||||
if (duplicate) {
|
||||
state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
|
||||
}
|
||||
}
|
||||
} else if ('[object Array]' === type) {
|
||||
if (block && (0 !== state.dump.length)) {
|
||||
writeBlockSequence(state, level, state.dump, compact);
|
||||
if (duplicate) {
|
||||
state.dump = '&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
|
||||
}
|
||||
} else {
|
||||
writeFlowSequence(state, level, state.dump);
|
||||
if (duplicate) {
|
||||
state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
|
||||
}
|
||||
}
|
||||
} else if ('[object String]' === type) {
|
||||
if ('?' !== state.tag) {
|
||||
writeScalar(state, state.dump, level);
|
||||
}
|
||||
} else {
|
||||
if (state.skipInvalid) {
|
||||
return false;
|
||||
}
|
||||
throw new YAMLException('unacceptable kind of an object to dump ' + type);
|
||||
}
|
||||
|
||||
if (null !== state.tag && '?' !== state.tag) {
|
||||
state.dump = '!<' + state.tag + '> ' + state.dump;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getDuplicateReferences(object, state) {
|
||||
var objects = [],
|
||||
duplicatesIndexes = [],
|
||||
index,
|
||||
length;
|
||||
|
||||
inspectNode(object, objects, duplicatesIndexes);
|
||||
|
||||
for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
|
||||
state.duplicates.push(objects[duplicatesIndexes[index]]);
|
||||
}
|
||||
state.usedDuplicates = new Array(length);
|
||||
}
|
||||
|
||||
function inspectNode(object, objects, duplicatesIndexes) {
|
||||
var type = _toString.call(object),
|
||||
objectKeyList,
|
||||
index,
|
||||
length;
|
||||
|
||||
if (null !== object && 'object' === typeof object) {
|
||||
index = objects.indexOf(object);
|
||||
if (-1 !== index) {
|
||||
if (-1 === duplicatesIndexes.indexOf(index)) {
|
||||
duplicatesIndexes.push(index);
|
||||
}
|
||||
} else {
|
||||
objects.push(object);
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
inspectNode(object[index], objects, duplicatesIndexes);
|
||||
}
|
||||
} else {
|
||||
objectKeyList = Object.keys(object);
|
||||
|
||||
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
|
||||
inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dump(input, options) {
|
||||
options = options || {};
|
||||
|
||||
var state = new State(options);
|
||||
|
||||
getDuplicateReferences(input, state);
|
||||
|
||||
if (writeNode(state, 0, input, true, true)) {
|
||||
return state.dump + '\n';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function safeDump(input, options) {
|
||||
return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
|
||||
}
|
||||
53
src/yaml-ast-parser/exception.ts
Normal file
53
src/yaml-ast-parser/exception.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import Mark from "./mark"
|
||||
|
||||
export default class YAMLException {
|
||||
message: string
|
||||
reason: string
|
||||
name: string
|
||||
mark: Mark
|
||||
isWarning: boolean
|
||||
|
||||
private static CLASS_IDENTIFIER = "yaml-ast-parser.YAMLException";
|
||||
|
||||
public static isInstance(instance: any): instance is YAMLException {
|
||||
if (instance != null && instance.getClassIdentifier
|
||||
&& typeof (instance.getClassIdentifier) == "function") {
|
||||
|
||||
for (let currentIdentifier of instance.getClassIdentifier()) {
|
||||
if (currentIdentifier == YAMLException.CLASS_IDENTIFIER) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getClassIdentifier(): string[] {
|
||||
var superIdentifiers = [];
|
||||
|
||||
return superIdentifiers.concat(YAMLException.CLASS_IDENTIFIER);
|
||||
}
|
||||
|
||||
constructor(reason: string, mark: Mark = null, isWarning = false) {
|
||||
this.name = 'YAMLException';
|
||||
this.reason = reason;
|
||||
this.mark = mark;
|
||||
this.message = this.toString(false);
|
||||
this.isWarning = isWarning;
|
||||
}
|
||||
|
||||
toString(compact: boolean = false) {
|
||||
var result;
|
||||
|
||||
result = 'JS-YAML: ' + (this.reason || '(unknown reason)');
|
||||
|
||||
if (!compact && this.mark) {
|
||||
result += ' ' + this.mark.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
22
src/yaml-ast-parser/index.ts
Normal file
22
src/yaml-ast-parser/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
/**
|
||||
* Created by kor on 06/05/15.
|
||||
*/
|
||||
|
||||
export { load, loadAll, safeLoad, safeLoadAll, LoadOptions } from './loader';
|
||||
export { dump, safeDump } from './dumper';
|
||||
|
||||
import Mark from "./mark"
|
||||
import YAMLException from './exception';
|
||||
|
||||
export * from './yamlAST'
|
||||
|
||||
export type Error = YAMLException
|
||||
|
||||
function deprecated(name) {
|
||||
return function () {
|
||||
throw new Error('Function ' + name + ' is deprecated and cannot be used.');
|
||||
};
|
||||
}
|
||||
|
||||
export * from './scalarInference'
|
||||
1844
src/yaml-ast-parser/loader.ts
Normal file
1844
src/yaml-ast-parser/loader.ts
Normal file
File diff suppressed because it is too large
Load diff
72
src/yaml-ast-parser/mark.ts
Normal file
72
src/yaml-ast-parser/mark.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import * as common from './common';
|
||||
|
||||
export default class Mark {
|
||||
|
||||
constructor(public name: string, public buffer: string, public position: number, public line: number, public column: number) {
|
||||
}
|
||||
|
||||
filePath: string;
|
||||
|
||||
toLineEnd: boolean;
|
||||
|
||||
getSnippet(indent: number = 0, maxLength: number = 75) {
|
||||
var head, start, tail, end, snippet;
|
||||
|
||||
if (!this.buffer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
indent = indent || 4;
|
||||
maxLength = maxLength || 75;
|
||||
|
||||
head = '';
|
||||
start = this.position;
|
||||
|
||||
while (start > 0 && -1 === '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(start - 1))) {
|
||||
start -= 1;
|
||||
if (this.position - start > (maxLength / 2 - 1)) {
|
||||
head = ' ... ';
|
||||
start += 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
tail = '';
|
||||
end = this.position;
|
||||
|
||||
while (end < this.buffer.length && -1 === '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(end))) {
|
||||
end += 1;
|
||||
if (end - this.position > (maxLength / 2 - 1)) {
|
||||
tail = ' ... ';
|
||||
end -= 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
snippet = this.buffer.slice(start, end);
|
||||
|
||||
return common.repeat(' ', indent) + head + snippet + tail + '\n' +
|
||||
common.repeat(' ', indent + this.position - start + head.length) + '^';
|
||||
}
|
||||
|
||||
toString(compact: boolean = true) {
|
||||
var snippet, where = '';
|
||||
|
||||
if (this.name) {
|
||||
where += 'in "' + this.name + '" ';
|
||||
}
|
||||
|
||||
where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1);
|
||||
|
||||
if (!compact) {
|
||||
snippet = this.getSnippet();
|
||||
|
||||
if (snippet) {
|
||||
where += ':\n' + snippet;
|
||||
}
|
||||
}
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
}
|
||||
98
src/yaml-ast-parser/scalarInference.ts
Normal file
98
src/yaml-ast-parser/scalarInference.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { YAMLScalar } from './yamlAST'
|
||||
|
||||
export function parseYamlBoolean(input: string): boolean {
|
||||
if (["true", "True", "TRUE"].lastIndexOf(input) >= 0) {
|
||||
return true;
|
||||
}
|
||||
else if (["false", "False", "FALSE"].lastIndexOf(input) >= 0) {
|
||||
return false;
|
||||
}
|
||||
throw `Invalid boolean "${input}"`
|
||||
}
|
||||
|
||||
function safeParseYamlInteger(input: string): number {
|
||||
// Use startsWith when es6 methods becomes available
|
||||
if (input.lastIndexOf('0o', 0) === 0) {
|
||||
return parseInt(input.substring(2), 8)
|
||||
}
|
||||
|
||||
return parseInt(input);
|
||||
}
|
||||
|
||||
export function parseYamlInteger(input: string): number {
|
||||
const result = safeParseYamlInteger(input)
|
||||
|
||||
if (isNaN(result)) {
|
||||
throw `Invalid integer "${input}"`
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseYamlFloat(input: string): number {
|
||||
|
||||
if ([".nan", ".NaN", ".NAN"].lastIndexOf(input) >= 0) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const infinity = /^([-+])?(?:\.inf|\.Inf|\.INF)$/
|
||||
const match = infinity.exec(input)
|
||||
if (match) {
|
||||
return (match[1] === '-') ? -Infinity : Infinity;
|
||||
}
|
||||
|
||||
const result = parseFloat(input)
|
||||
|
||||
if (!isNaN(result)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw `Invalid float "${input}"`
|
||||
}
|
||||
|
||||
export enum ScalarType {
|
||||
null, bool, int, float, string
|
||||
}
|
||||
|
||||
/** Determines the type of a scalar according to
|
||||
* the YAML 1.2 Core Schema (http://www.yaml.org/spec/1.2/spec.html#id2804923)
|
||||
*/
|
||||
export function determineScalarType(node: YAMLScalar): ScalarType {
|
||||
if (node === undefined) {
|
||||
return ScalarType.null;
|
||||
}
|
||||
|
||||
if (node.doubleQuoted || !node.plainScalar || node['singleQuoted']) {
|
||||
return ScalarType.string
|
||||
}
|
||||
|
||||
const value = node.value;
|
||||
|
||||
if (["null", "Null", "NULL", "~", ''].indexOf(value) >= 0) {
|
||||
return ScalarType.null;
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return ScalarType.null;
|
||||
}
|
||||
|
||||
if (["true", "True", "TRUE", "false", "False", "FALSE"].indexOf(value) >= 0) {
|
||||
return ScalarType.bool;
|
||||
}
|
||||
|
||||
const base10 = /^[-+]?[0-9]+$/
|
||||
const base8 = /^0o[0-7]+$/
|
||||
const base16 = /^0x[0-9a-fA-F]+$/
|
||||
|
||||
if (base10.test(value) || base8.test(value) || base16.test(value)) {
|
||||
return ScalarType.int;
|
||||
}
|
||||
|
||||
const float = /^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$/
|
||||
const infinity = /^[-+]?(\.inf|\.Inf|\.INF)$/
|
||||
if (float.test(value) || infinity.test(value) || [".nan", ".NaN", ".NAN"].indexOf(value) >= 0) {
|
||||
return ScalarType.float;
|
||||
}
|
||||
|
||||
return ScalarType.string;
|
||||
}
|
||||
112
src/yaml-ast-parser/schema.ts
Normal file
112
src/yaml-ast-parser/schema.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
|
||||
/*eslint-disable max-len*/
|
||||
|
||||
import * as common from './common';
|
||||
import YAMLException from './exception';
|
||||
import { Type } from './type';
|
||||
|
||||
|
||||
function compileList(schema: Schema, name, result) {
|
||||
var exclude = [];
|
||||
|
||||
schema.include.forEach(function (includedSchema) {
|
||||
result = compileList(includedSchema, name, result);
|
||||
});
|
||||
|
||||
schema[name].forEach(function (currentType) {
|
||||
result.forEach(function (previousType, previousIndex) {
|
||||
if (previousType.tag === currentType.tag) {
|
||||
exclude.push(previousIndex);
|
||||
}
|
||||
});
|
||||
|
||||
result.push(currentType);
|
||||
});
|
||||
|
||||
return result.filter(function (type, index) {
|
||||
return -1 === exclude.indexOf(index);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function compileMap(/* lists... */) {
|
||||
var result = {}, index, length;
|
||||
|
||||
function collectType(type) {
|
||||
result[type.tag] = type;
|
||||
}
|
||||
|
||||
for (index = 0, length = arguments.length; index < length; index += 1) {
|
||||
arguments[index].forEach(collectType);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export interface SchemaDefinition {
|
||||
include?: Schema[]
|
||||
implicit?: Type[]
|
||||
explicit?: Type[]
|
||||
}
|
||||
|
||||
export class Schema {
|
||||
|
||||
include: Schema[]
|
||||
implicit: Type[]
|
||||
explicit: Type[]
|
||||
|
||||
compiledImplicit: any[]
|
||||
compiledExplicit: any[]
|
||||
compiledTypeMap: any[]
|
||||
constructor(definition: SchemaDefinition) {
|
||||
this.include = definition.include || [];
|
||||
this.implicit = definition.implicit || [];
|
||||
this.explicit = definition.explicit || [];
|
||||
|
||||
this.implicit.forEach(function (type) {
|
||||
if (type.loadKind && 'scalar' !== type.loadKind) {
|
||||
throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.');
|
||||
}
|
||||
});
|
||||
|
||||
this.compiledImplicit = compileList(this, 'implicit', []);
|
||||
this.compiledExplicit = compileList(this, 'explicit', []);
|
||||
this.compiledTypeMap = (<any>compileMap)(this.compiledImplicit, this.compiledExplicit);
|
||||
}
|
||||
|
||||
static DEFAULT = null;
|
||||
static create = function createSchema() {
|
||||
var schemas, types;
|
||||
|
||||
switch (arguments.length) {
|
||||
case 1:
|
||||
schemas = Schema.DEFAULT;
|
||||
types = arguments[0];
|
||||
break;
|
||||
|
||||
case 2:
|
||||
schemas = arguments[0];
|
||||
types = arguments[1];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new YAMLException('Wrong number of arguments for Schema.create function');
|
||||
}
|
||||
|
||||
schemas = common.toArray(schemas);
|
||||
types = common.toArray(types);
|
||||
|
||||
if (!schemas.every(function (schema) { return schema instanceof Schema; })) {
|
||||
throw new YAMLException('Specified list of super schemas (or a single Schema object) contains a non-Schema object.');
|
||||
}
|
||||
|
||||
if (!types.every(function (type) { return type instanceof Type; })) {
|
||||
throw new YAMLException('Specified list of YAML types (or a single Type object) contains a non-Type object.');
|
||||
}
|
||||
|
||||
return new Schema({
|
||||
include: schemas,
|
||||
explicit: types
|
||||
});
|
||||
}
|
||||
}
|
||||
15
src/yaml-ast-parser/schema/core.ts
Normal file
15
src/yaml-ast-parser/schema/core.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
// Standard YAML's Core schema.
|
||||
// http://www.yaml.org/spec/1.2/spec.html#id2804923
|
||||
//
|
||||
// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.
|
||||
// So, Core schema has no distinctions from JSON schema is JS-YAML.
|
||||
|
||||
import { Schema } from '../schema';
|
||||
import json from './json';
|
||||
|
||||
export default new Schema({
|
||||
include: [
|
||||
json
|
||||
]
|
||||
});
|
||||
28
src/yaml-ast-parser/schema/default_full.ts
Normal file
28
src/yaml-ast-parser/schema/default_full.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
// JS-YAML's default schema for `load` function.
|
||||
// It is not described in the YAML specification.
|
||||
//
|
||||
// This schema is based on JS-YAML's default safe schema and includes
|
||||
// JavaScript-specific types: !!js/undefined, !!js/regexp and !!js/function.
|
||||
//
|
||||
// Also this schema is used as default base schema at `Schema.create` function.
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
import DefaultSafe from './default_safe';
|
||||
|
||||
import UndefinedType from '../type/js/undefined';
|
||||
import RegexpType from '../type/js/regexp';
|
||||
|
||||
var schema = new Schema({
|
||||
include: [
|
||||
DefaultSafe
|
||||
],
|
||||
explicit: [
|
||||
UndefinedType,
|
||||
RegexpType
|
||||
|
||||
]
|
||||
})
|
||||
Schema.DEFAULT = schema;
|
||||
export default schema;
|
||||
34
src/yaml-ast-parser/schema/default_safe.ts
Normal file
34
src/yaml-ast-parser/schema/default_safe.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
|
||||
// JS-YAML's default schema for `safeLoad` function.
|
||||
// It is not described in the YAML specification.
|
||||
//
|
||||
// This schema is based on standard YAML's Core schema and includes most of
|
||||
// extra types described at YAML tag repository. (http://yaml.org/type/)
|
||||
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
import Core from './core';
|
||||
import TimestampType from '../type/timestamp';
|
||||
import MergeType from '../type/merge';
|
||||
import BinaryType from '../type/binary';
|
||||
import OmapType from '../type/omap';
|
||||
import PairsType from '../type/pairs';
|
||||
import SetType from '../type/set';
|
||||
|
||||
export default new Schema({
|
||||
include: [
|
||||
Core
|
||||
],
|
||||
implicit: [
|
||||
TimestampType,
|
||||
MergeType
|
||||
],
|
||||
explicit: [
|
||||
BinaryType,
|
||||
OmapType,
|
||||
PairsType,
|
||||
SetType
|
||||
]
|
||||
})
|
||||
18
src/yaml-ast-parser/schema/failsafe.ts
Normal file
18
src/yaml-ast-parser/schema/failsafe.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
// Standard YAML's Failsafe schema.
|
||||
// http://www.yaml.org/spec/1.2/spec.html#id2802346
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
import StrType from '../type/str';
|
||||
import SeqType from '../type/seq';
|
||||
import MapType from '../type/map';
|
||||
|
||||
export default new Schema({
|
||||
explicit: [
|
||||
StrType,
|
||||
SeqType,
|
||||
MapType
|
||||
]
|
||||
});
|
||||
28
src/yaml-ast-parser/schema/json.ts
Normal file
28
src/yaml-ast-parser/schema/json.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
// Standard YAML's JSON schema.
|
||||
// http://www.yaml.org/spec/1.2/spec.html#id2803231
|
||||
//
|
||||
// NOTE: JS-YAML does not support schema-specific tag resolution restrictions.
|
||||
// So, this schema is not such strict as defined in the YAML specification.
|
||||
// It allows numbers in binary notaion, use `Null` and `NULL` as `null`, etc.
|
||||
|
||||
import { Schema } from '../schema';
|
||||
|
||||
import failsafe from './failsafe';
|
||||
|
||||
import NullType from '../type/null';
|
||||
import BoolType from '../type/bool';
|
||||
import IntType from '../type/int';
|
||||
import FloatType from '../type/float';
|
||||
|
||||
export default new Schema({
|
||||
include: [
|
||||
failsafe
|
||||
],
|
||||
implicit: [
|
||||
NullType,
|
||||
BoolType,
|
||||
IntType,
|
||||
FloatType
|
||||
]
|
||||
});
|
||||
73
src/yaml-ast-parser/type.ts
Normal file
73
src/yaml-ast-parser/type.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
import YAMLException = require('./exception');
|
||||
|
||||
var TYPE_CONSTRUCTOR_OPTIONS = [
|
||||
'kind',
|
||||
'resolve',
|
||||
'construct',
|
||||
'instanceOf',
|
||||
'predicate',
|
||||
'represent',
|
||||
'defaultStyle',
|
||||
'styleAliases'
|
||||
];
|
||||
|
||||
var YAML_NODE_KINDS = [
|
||||
'scalar',
|
||||
'sequence',
|
||||
'mapping'
|
||||
];
|
||||
|
||||
function compileStyleAliases(map) {
|
||||
var result = {};
|
||||
|
||||
if (null !== map) {
|
||||
Object.keys(map).forEach(function (style) {
|
||||
map[style].forEach(function (alias) {
|
||||
result[String(alias)] = style;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export class Type {
|
||||
|
||||
tag;
|
||||
kind;
|
||||
resolve;
|
||||
construct;
|
||||
instanceOf;
|
||||
predicate;
|
||||
represent;
|
||||
defaultStyle;
|
||||
styleAliases;
|
||||
loadKind;
|
||||
|
||||
constructor(tag, options) {
|
||||
options = options || {};
|
||||
|
||||
Object.keys(options).forEach(function (name) {
|
||||
if (-1 === TYPE_CONSTRUCTOR_OPTIONS.indexOf(name)) {
|
||||
throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.');
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Add tag format check.
|
||||
this.tag = tag;
|
||||
this.kind = options['kind'] || null;
|
||||
this.resolve = options['resolve'] || function () { return true; };
|
||||
this.construct = options['construct'] || function (data) { return data; };
|
||||
this.instanceOf = options['instanceOf'] || null;
|
||||
this.predicate = options['predicate'] || null;
|
||||
this.represent = options['represent'] || null;
|
||||
this.defaultStyle = options['defaultStyle'] || null;
|
||||
this.styleAliases = compileStyleAliases(options['styleAliases'] || null);
|
||||
|
||||
if (-1 === YAML_NODE_KINDS.indexOf(this.kind)) {
|
||||
throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.');
|
||||
}
|
||||
}
|
||||
}
|
||||
133
src/yaml-ast-parser/type/binary.ts
Normal file
133
src/yaml-ast-parser/type/binary.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
|
||||
/*eslint-disable no-bitwise*/
|
||||
|
||||
// A trick for browserified version.
|
||||
// Since we make browserifier to ignore `buffer` module, NodeBuffer will be undefined
|
||||
declare var Buffer: any;
|
||||
|
||||
import { Type } from '../type';
|
||||
|
||||
// [ 64, 65, 66 ] -> [ padding, CR, LF ]
|
||||
var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r';
|
||||
|
||||
|
||||
function resolveYamlBinary(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var code, idx, bitlen = 0, len = 0, max = data.length, map = BASE64_MAP;
|
||||
|
||||
// Convert one by one.
|
||||
for (idx = 0; idx < max; idx++) {
|
||||
code = map.indexOf(data.charAt(idx));
|
||||
|
||||
// Skip CR/LF
|
||||
if (code > 64) { continue; }
|
||||
|
||||
// Fail on illegal characters
|
||||
if (code < 0) { return false; }
|
||||
|
||||
bitlen += 6;
|
||||
}
|
||||
|
||||
// If there are any bits left, source was corrupted
|
||||
return (bitlen % 8) === 0;
|
||||
}
|
||||
|
||||
function constructYamlBinary(data) {
|
||||
var code, idx, tailbits,
|
||||
input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan
|
||||
max = input.length,
|
||||
map = BASE64_MAP,
|
||||
bits = 0,
|
||||
result = [];
|
||||
|
||||
// Collect by 6*4 bits (3 bytes)
|
||||
|
||||
for (idx = 0; idx < max; idx++) {
|
||||
if ((idx % 4 === 0) && idx) {
|
||||
result.push((bits >> 16) & 0xFF);
|
||||
result.push((bits >> 8) & 0xFF);
|
||||
result.push(bits & 0xFF);
|
||||
}
|
||||
|
||||
bits = (bits << 6) | map.indexOf(input.charAt(idx));
|
||||
}
|
||||
|
||||
// Dump tail
|
||||
|
||||
tailbits = (max % 4) * 6;
|
||||
|
||||
if (tailbits === 0) {
|
||||
result.push((bits >> 16) & 0xFF);
|
||||
result.push((bits >> 8) & 0xFF);
|
||||
result.push(bits & 0xFF);
|
||||
} else if (tailbits === 18) {
|
||||
result.push((bits >> 10) & 0xFF);
|
||||
result.push((bits >> 2) & 0xFF);
|
||||
} else if (tailbits === 12) {
|
||||
result.push((bits >> 4) & 0xFF);
|
||||
}
|
||||
|
||||
// Wrap into Buffer for NodeJS and leave Array for browser
|
||||
if (Buffer) {
|
||||
return new Buffer(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function representYamlBinary(object /*, style*/) {
|
||||
var result = '', bits = 0, idx, tail,
|
||||
max = object.length,
|
||||
map = BASE64_MAP;
|
||||
|
||||
// Convert every three bytes to 4 ASCII characters.
|
||||
|
||||
for (idx = 0; idx < max; idx++) {
|
||||
if ((idx % 3 === 0) && idx) {
|
||||
result += map[(bits >> 18) & 0x3F];
|
||||
result += map[(bits >> 12) & 0x3F];
|
||||
result += map[(bits >> 6) & 0x3F];
|
||||
result += map[bits & 0x3F];
|
||||
}
|
||||
|
||||
bits = (bits << 8) + object[idx];
|
||||
}
|
||||
|
||||
// Dump tail
|
||||
|
||||
tail = max % 3;
|
||||
|
||||
if (tail === 0) {
|
||||
result += map[(bits >> 18) & 0x3F];
|
||||
result += map[(bits >> 12) & 0x3F];
|
||||
result += map[(bits >> 6) & 0x3F];
|
||||
result += map[bits & 0x3F];
|
||||
} else if (tail === 2) {
|
||||
result += map[(bits >> 10) & 0x3F];
|
||||
result += map[(bits >> 4) & 0x3F];
|
||||
result += map[(bits << 2) & 0x3F];
|
||||
result += map[64];
|
||||
} else if (tail === 1) {
|
||||
result += map[(bits >> 2) & 0x3F];
|
||||
result += map[(bits << 4) & 0x3F];
|
||||
result += map[64];
|
||||
result += map[64];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isBinary(object) {
|
||||
return Buffer && Buffer.isBuffer(object);
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:binary', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlBinary,
|
||||
construct: constructYamlBinary,
|
||||
predicate: isBinary,
|
||||
represent: representYamlBinary
|
||||
});
|
||||
36
src/yaml-ast-parser/type/bool.ts
Normal file
36
src/yaml-ast-parser/type/bool.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
function resolveYamlBoolean(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var max = data.length;
|
||||
|
||||
return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) ||
|
||||
(max === 5 && (data === 'false' || data === 'False' || data === 'FALSE'));
|
||||
}
|
||||
|
||||
function constructYamlBoolean(data) {
|
||||
return data === 'true' ||
|
||||
data === 'True' ||
|
||||
data === 'TRUE';
|
||||
}
|
||||
|
||||
function isBoolean(object) {
|
||||
return '[object Boolean]' === Object.prototype.toString.call(object);
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:bool', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlBoolean,
|
||||
construct: constructYamlBoolean,
|
||||
predicate: isBoolean,
|
||||
represent: {
|
||||
lowercase: function (object) { return object ? 'true' : 'false'; },
|
||||
uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; },
|
||||
camelcase: function (object) { return object ? 'True' : 'False'; }
|
||||
},
|
||||
defaultStyle: 'lowercase'
|
||||
});
|
||||
107
src/yaml-ast-parser/type/float.ts
Normal file
107
src/yaml-ast-parser/type/float.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
|
||||
import * as common from '../common';
|
||||
import { Type } from '../type';
|
||||
|
||||
var YAML_FLOAT_PATTERN = new RegExp(
|
||||
'^(?:[-+]?(?:[0-9][0-9_]*)\\.[0-9_]*(?:[eE][-+][0-9]+)?' +
|
||||
'|\\.[0-9_]+(?:[eE][-+][0-9]+)?' +
|
||||
'|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*' +
|
||||
'|[-+]?\\.(?:inf|Inf|INF)' +
|
||||
'|\\.(?:nan|NaN|NAN))$');
|
||||
|
||||
function resolveYamlFloat(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var value, sign, base, digits;
|
||||
|
||||
if (!YAML_FLOAT_PATTERN.test(data)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructYamlFloat(data) {
|
||||
var value, sign, base, digits;
|
||||
|
||||
value = data.replace(/_/g, '').toLowerCase();
|
||||
sign = '-' === value[0] ? -1 : 1;
|
||||
digits = [];
|
||||
|
||||
if (0 <= '+-'.indexOf(value[0])) {
|
||||
value = value.slice(1);
|
||||
}
|
||||
|
||||
if ('.inf' === value) {
|
||||
return (1 === sign) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
|
||||
|
||||
} else if ('.nan' === value) {
|
||||
return NaN;
|
||||
|
||||
} else if (0 <= value.indexOf(':')) {
|
||||
value.split(':').forEach(function (v) {
|
||||
digits.unshift((<any>parseFloat)(v, 10));
|
||||
});
|
||||
|
||||
value = 0.0;
|
||||
base = 1;
|
||||
|
||||
digits.forEach(function (d) {
|
||||
value += d * base;
|
||||
base *= 60;
|
||||
});
|
||||
|
||||
return sign * value;
|
||||
|
||||
}
|
||||
return sign * (<any>parseFloat)(value, 10);
|
||||
}
|
||||
|
||||
function representYamlFloat(object, style) {
|
||||
if (isNaN(object)) {
|
||||
switch (style) {
|
||||
case 'lowercase':
|
||||
return '.nan';
|
||||
case 'uppercase':
|
||||
return '.NAN';
|
||||
case 'camelcase':
|
||||
return '.NaN';
|
||||
}
|
||||
} else if (Number.POSITIVE_INFINITY === object) {
|
||||
switch (style) {
|
||||
case 'lowercase':
|
||||
return '.inf';
|
||||
case 'uppercase':
|
||||
return '.INF';
|
||||
case 'camelcase':
|
||||
return '.Inf';
|
||||
}
|
||||
} else if (Number.NEGATIVE_INFINITY === object) {
|
||||
switch (style) {
|
||||
case 'lowercase':
|
||||
return '-.inf';
|
||||
case 'uppercase':
|
||||
return '-.INF';
|
||||
case 'camelcase':
|
||||
return '-.Inf';
|
||||
}
|
||||
} else if (common.isNegativeZero(object)) {
|
||||
return '-0.0';
|
||||
}
|
||||
return object.toString(10);
|
||||
}
|
||||
|
||||
function isFloat(object) {
|
||||
return ('[object Number]' === Object.prototype.toString.call(object)) &&
|
||||
(0 !== object % 1 || common.isNegativeZero(object));
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:float', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlFloat,
|
||||
construct: constructYamlFloat,
|
||||
predicate: isFloat,
|
||||
represent: representYamlFloat,
|
||||
defaultStyle: 'lowercase'
|
||||
});
|
||||
183
src/yaml-ast-parser/type/int.ts
Normal file
183
src/yaml-ast-parser/type/int.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
|
||||
|
||||
import * as common from '../common';
|
||||
import { Type } from '../type';
|
||||
|
||||
function isHexCode(c) {
|
||||
return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) ||
|
||||
((0x41/* A */ <= c) && (c <= 0x46/* F */)) ||
|
||||
((0x61/* a */ <= c) && (c <= 0x66/* f */));
|
||||
}
|
||||
|
||||
function isOctCode(c) {
|
||||
return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */));
|
||||
}
|
||||
|
||||
function isDecCode(c) {
|
||||
return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */));
|
||||
}
|
||||
|
||||
function resolveYamlInteger(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var max = data.length,
|
||||
index = 0,
|
||||
hasDigits = false,
|
||||
ch;
|
||||
|
||||
if (!max) { return false; }
|
||||
|
||||
ch = data[index];
|
||||
|
||||
// sign
|
||||
if (ch === '-' || ch === '+') {
|
||||
ch = data[++index];
|
||||
}
|
||||
|
||||
if (ch === '0') {
|
||||
// 0
|
||||
if (index + 1 === max) { return true; }
|
||||
ch = data[++index];
|
||||
|
||||
// base 2, base 8, base 16
|
||||
|
||||
if (ch === 'b') {
|
||||
// base 2
|
||||
index++;
|
||||
|
||||
for (; index < max; index++) {
|
||||
ch = data[index];
|
||||
if (ch === '_') { continue; }
|
||||
if (ch !== '0' && ch !== '1') {
|
||||
return false;
|
||||
}
|
||||
hasDigits = true;
|
||||
}
|
||||
return hasDigits;
|
||||
}
|
||||
|
||||
|
||||
if (ch === 'x') {
|
||||
// base 16
|
||||
index++;
|
||||
|
||||
for (; index < max; index++) {
|
||||
ch = data[index];
|
||||
if (ch === '_') { continue; }
|
||||
if (!isHexCode(data.charCodeAt(index))) {
|
||||
return false;
|
||||
}
|
||||
hasDigits = true;
|
||||
}
|
||||
return hasDigits;
|
||||
}
|
||||
|
||||
// base 8
|
||||
for (; index < max; index++) {
|
||||
ch = data[index];
|
||||
if (ch === '_') { continue; }
|
||||
if (!isOctCode(data.charCodeAt(index))) {
|
||||
return false;
|
||||
}
|
||||
hasDigits = true;
|
||||
}
|
||||
return hasDigits;
|
||||
}
|
||||
|
||||
// base 10 (except 0) or base 60
|
||||
|
||||
for (; index < max; index++) {
|
||||
ch = data[index];
|
||||
if (ch === '_') { continue; }
|
||||
if (ch === ':') { break; }
|
||||
if (!isDecCode(data.charCodeAt(index))) {
|
||||
return false;
|
||||
}
|
||||
hasDigits = true;
|
||||
}
|
||||
|
||||
if (!hasDigits) { return false; }
|
||||
|
||||
// if !base60 - done;
|
||||
if (ch !== ':') { return true; }
|
||||
|
||||
// base60 almost not used, no needs to optimize
|
||||
return /^(:[0-5]?[0-9])+$/.test(data.slice(index));
|
||||
}
|
||||
|
||||
function constructYamlInteger(data) {
|
||||
var value = data, sign = 1, ch, base, digits = [];
|
||||
|
||||
if (value.indexOf('_') !== -1) {
|
||||
value = value.replace(/_/g, '');
|
||||
}
|
||||
|
||||
ch = value[0];
|
||||
|
||||
if (ch === '-' || ch === '+') {
|
||||
if (ch === '-') { sign = -1; }
|
||||
value = value.slice(1);
|
||||
ch = value[0];
|
||||
}
|
||||
|
||||
if ('0' === value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ch === '0') {
|
||||
if (value[1] === 'b') {
|
||||
return sign * parseInt(value.slice(2), 2);
|
||||
}
|
||||
if (value[1] === 'x') {
|
||||
return sign * parseInt(value, 16);
|
||||
}
|
||||
return sign * parseInt(value, 8);
|
||||
|
||||
}
|
||||
|
||||
if (value.indexOf(':') !== -1) {
|
||||
value.split(':').forEach(function (v) {
|
||||
digits.unshift(parseInt(v, 10));
|
||||
});
|
||||
|
||||
value = 0;
|
||||
base = 1;
|
||||
|
||||
digits.forEach(function (d) {
|
||||
value += (d * base);
|
||||
base *= 60;
|
||||
});
|
||||
|
||||
return sign * value;
|
||||
|
||||
}
|
||||
|
||||
return sign * parseInt(value, 10);
|
||||
}
|
||||
|
||||
function isInteger(object) {
|
||||
return ('[object Number]' === Object.prototype.toString.call(object)) &&
|
||||
(0 === object % 1 && !common.isNegativeZero(object));
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:int', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlInteger,
|
||||
construct: constructYamlInteger,
|
||||
predicate: isInteger,
|
||||
represent: {
|
||||
binary: function (object) { return '0b' + object.toString(2); },
|
||||
octal: function (object) { return '0' + object.toString(8); },
|
||||
decimal: function (object) { return object.toString(10); },
|
||||
hexadecimal: function (object) { return '0x' + object.toString(16).toUpperCase(); }
|
||||
},
|
||||
defaultStyle: 'decimal',
|
||||
styleAliases: {
|
||||
binary: [2, 'bin'],
|
||||
octal: [8, 'oct'],
|
||||
decimal: [10, 'dec'],
|
||||
hexadecimal: [16, 'hex']
|
||||
}
|
||||
});
|
||||
79
src/yaml-ast-parser/type/js/function.ts
Normal file
79
src/yaml-ast-parser/type/js/function.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
declare var esprima: any;
|
||||
|
||||
// Browserified version does not have esprima
|
||||
//
|
||||
// 1. For node.js just require module as deps
|
||||
// 2. For browser try to require mudule via external AMD system.
|
||||
// If not found - try to fallback to window.esprima. If not
|
||||
// found too - then fail to parse.
|
||||
//
|
||||
|
||||
import { Type } from '../../type';
|
||||
|
||||
function resolveJavascriptFunction(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
var source = '(' + data + ')',
|
||||
ast = esprima.parse(source, { range: true }),
|
||||
params = [],
|
||||
body;
|
||||
|
||||
if ('Program' !== ast.type ||
|
||||
1 !== ast.body.length ||
|
||||
'ExpressionStatement' !== ast.body[0].type ||
|
||||
'FunctionExpression' !== ast.body[0]['expression'].type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function constructJavascriptFunction(data) {
|
||||
/*jslint evil:true*/
|
||||
|
||||
var source = '(' + data + ')',
|
||||
ast = esprima.parse(source, { range: true }),
|
||||
params: string[] = [],
|
||||
body;
|
||||
|
||||
if ('Program' !== ast.type ||
|
||||
1 !== ast.body.length ||
|
||||
'ExpressionStatement' !== ast.body[0].type ||
|
||||
'FunctionExpression' !== ast.body[0]['expression'].type) {
|
||||
throw new Error('Failed to resolve function');
|
||||
}
|
||||
|
||||
ast.body[0]['expression'].params.forEach(function (param) {
|
||||
params.push(param.name);
|
||||
});
|
||||
|
||||
body = ast.body[0]['expression'].body.range;
|
||||
|
||||
// Esprima's ranges include the first '{' and the last '}' characters on
|
||||
// function expressions. So cut them out.
|
||||
/*eslint-disable no-new-func*/
|
||||
return new (<any>Function)(params, source.slice(body[0] + 1, body[1] - 1));
|
||||
}
|
||||
|
||||
function representJavascriptFunction(object /*, style*/) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
function isFunction(object) {
|
||||
return '[object Function]' === Object.prototype.toString.call(object);
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:js/function', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveJavascriptFunction,
|
||||
construct: constructJavascriptFunction,
|
||||
predicate: isFunction,
|
||||
represent: representJavascriptFunction
|
||||
});
|
||||
84
src/yaml-ast-parser/type/js/regexp.ts
Normal file
84
src/yaml-ast-parser/type/js/regexp.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
|
||||
import { Type } from '../../type';
|
||||
|
||||
function resolveJavascriptRegExp(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 === data.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var regexp = data,
|
||||
tail = /\/([gim]*)$/.exec(data),
|
||||
modifiers = '';
|
||||
|
||||
// if regexp starts with '/' it can have modifiers and must be properly closed
|
||||
// `/foo/gim` - modifiers tail can be maximum 3 chars
|
||||
if ('/' === regexp[0]) {
|
||||
if (tail) {
|
||||
modifiers = tail[1];
|
||||
}
|
||||
|
||||
if (modifiers.length > 3) { return false; }
|
||||
// if expression starts with /, is should be properly terminated
|
||||
if (regexp[regexp.length - modifiers.length - 1] !== '/') { return false; }
|
||||
|
||||
regexp = regexp.slice(1, regexp.length - modifiers.length - 1);
|
||||
}
|
||||
|
||||
try {
|
||||
var dummy = new RegExp(regexp, modifiers);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function constructJavascriptRegExp(data) {
|
||||
var regexp = data,
|
||||
tail = /\/([gim]*)$/.exec(data),
|
||||
modifiers = '';
|
||||
|
||||
// `/foo/gim` - tail can be maximum 4 chars
|
||||
if ('/' === regexp[0]) {
|
||||
if (tail) {
|
||||
modifiers = tail[1];
|
||||
}
|
||||
regexp = regexp.slice(1, regexp.length - modifiers.length - 1);
|
||||
}
|
||||
|
||||
return new RegExp(regexp, modifiers);
|
||||
}
|
||||
|
||||
function representJavascriptRegExp(object /*, style*/) {
|
||||
var result = '/' + object.source + '/';
|
||||
|
||||
if (object.global) {
|
||||
result += 'g';
|
||||
}
|
||||
|
||||
if (object.multiline) {
|
||||
result += 'm';
|
||||
}
|
||||
|
||||
if (object.ignoreCase) {
|
||||
result += 'i';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isRegExp(object) {
|
||||
return '[object RegExp]' === Object.prototype.toString.call(object);
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:js/regexp', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveJavascriptRegExp,
|
||||
construct: constructJavascriptRegExp,
|
||||
predicate: isRegExp,
|
||||
represent: representJavascriptRegExp
|
||||
});
|
||||
28
src/yaml-ast-parser/type/js/undefined.ts
Normal file
28
src/yaml-ast-parser/type/js/undefined.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
|
||||
import { Type } from '../../type';
|
||||
|
||||
function resolveJavascriptUndefined() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructJavascriptUndefined() {
|
||||
/*eslint-disable no-undefined*/
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function representJavascriptUndefined() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function isUndefined(object) {
|
||||
return 'undefined' === typeof object;
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:js/undefined', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveJavascriptUndefined,
|
||||
construct: constructJavascriptUndefined,
|
||||
predicate: isUndefined,
|
||||
represent: representJavascriptUndefined
|
||||
});
|
||||
7
src/yaml-ast-parser/type/map.ts
Normal file
7
src/yaml-ast-parser/type/map.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
export default new Type('tag:yaml.org,2002:map', {
|
||||
kind: 'mapping',
|
||||
construct: function (data) { return null !== data ? data : {}; }
|
||||
});
|
||||
12
src/yaml-ast-parser/type/merge.ts
Normal file
12
src/yaml-ast-parser/type/merge.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
import { Type } from '../type';
|
||||
|
||||
function resolveYamlMerge(data) {
|
||||
return '<<' === data || null === data;
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:merge', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlMerge
|
||||
});
|
||||
35
src/yaml-ast-parser/type/null.ts
Normal file
35
src/yaml-ast-parser/type/null.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
function resolveYamlNull(data) {
|
||||
if (null === data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var max = data.length;
|
||||
|
||||
return (max === 1 && data === '~') ||
|
||||
(max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
|
||||
}
|
||||
|
||||
function constructYamlNull() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function isNull(object) {
|
||||
return null === object;
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:null', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlNull,
|
||||
construct: constructYamlNull,
|
||||
predicate: isNull,
|
||||
represent: {
|
||||
canonical: function () { return '~'; },
|
||||
lowercase: function () { return 'null'; },
|
||||
uppercase: function () { return 'NULL'; },
|
||||
camelcase: function () { return 'Null'; }
|
||||
},
|
||||
defaultStyle: 'lowercase'
|
||||
});
|
||||
55
src/yaml-ast-parser/type/omap.ts
Normal file
55
src/yaml-ast-parser/type/omap.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
var _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var _toString = Object.prototype.toString;
|
||||
|
||||
function resolveYamlOmap(data) {
|
||||
if (null === data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var objectKeys = [], index, length, pair, pairKey, pairHasKey,
|
||||
object = data;
|
||||
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
pair = object[index];
|
||||
pairHasKey = false;
|
||||
|
||||
if ('[object Object]' !== _toString.call(pair)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (pairKey in pair) {
|
||||
if (_hasOwnProperty.call(pair, pairKey)) {
|
||||
if (!pairHasKey) {
|
||||
pairHasKey = true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pairHasKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (-1 === objectKeys.indexOf(pairKey)) {
|
||||
objectKeys.push(pairKey);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructYamlOmap(data) {
|
||||
return null !== data ? data : [];
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:omap', {
|
||||
kind: 'sequence',
|
||||
resolve: resolveYamlOmap,
|
||||
construct: constructYamlOmap
|
||||
});
|
||||
74
src/yaml-ast-parser/type/pairs.ts
Normal file
74
src/yaml-ast-parser/type/pairs.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
|
||||
import { Type } from '../type';
|
||||
import * as ast from "../yamlAST";
|
||||
|
||||
var _toString = Object.prototype.toString;
|
||||
|
||||
function resolveYamlPairs(data) {
|
||||
if (null === data) {
|
||||
return true;
|
||||
}
|
||||
if (data.kind != ast.Kind.SEQ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var index, length, pair, keys, result,
|
||||
object = data.items;
|
||||
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
pair = object[index];
|
||||
|
||||
if ('[object Object]' !== _toString.call(pair)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Array.isArray(pair.mappings)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (1 !== pair.mappings.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructYamlPairs(data) {
|
||||
if (null === data || !Array.isArray(data.items)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let index, length, keys, result,
|
||||
object = data.items;
|
||||
|
||||
result = ast.newItems();
|
||||
result.parent = data.parent;
|
||||
result.startPosition = data.startPosition;
|
||||
result.endPosition = data.endPosition;
|
||||
|
||||
for (index = 0, length = object.length; index < length; index += 1) {
|
||||
let pair = object[index];
|
||||
|
||||
let mapping = pair.mappings[0];
|
||||
|
||||
let pairSeq = ast.newItems();
|
||||
pairSeq.parent = result;
|
||||
pairSeq.startPosition = mapping.key.startPosition
|
||||
pairSeq.endPosition = mapping.value.startPosition
|
||||
mapping.key.parent = pairSeq;
|
||||
mapping.value.parent = pairSeq;
|
||||
pairSeq.items = [mapping.key, mapping.value];
|
||||
|
||||
result.items.push(pairSeq);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:pairs', {
|
||||
kind: 'sequence',
|
||||
resolve: resolveYamlPairs,
|
||||
construct: constructYamlPairs
|
||||
});
|
||||
8
src/yaml-ast-parser/type/seq.ts
Normal file
8
src/yaml-ast-parser/type/seq.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
|
||||
import { Type } from '../type';
|
||||
|
||||
export default new Type('tag:yaml.org,2002:seq', {
|
||||
kind: 'sequence',
|
||||
construct: function (data) { return null !== data ? data : []; }
|
||||
});
|
||||
28
src/yaml-ast-parser/type/set.ts
Normal file
28
src/yaml-ast-parser/type/set.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
|
||||
import { Type } from '../type';
|
||||
import * as ast from "../yamlAST";
|
||||
|
||||
var _hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
function resolveYamlSet(data) {
|
||||
if (null === data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data.kind != ast.Kind.MAP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructYamlSet(data) {
|
||||
return null !== data ? data : {};
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:set', {
|
||||
kind: 'mapping',
|
||||
resolve: resolveYamlSet,
|
||||
construct: constructYamlSet
|
||||
});
|
||||
7
src/yaml-ast-parser/type/str.ts
Normal file
7
src/yaml-ast-parser/type/str.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
export default new Type('tag:yaml.org,2002:str', {
|
||||
kind: 'scalar',
|
||||
construct: function (data) { return null !== data ? data : ''; }
|
||||
});
|
||||
97
src/yaml-ast-parser/type/timestamp.ts
Normal file
97
src/yaml-ast-parser/type/timestamp.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
|
||||
import { Type } from '../type';
|
||||
|
||||
var YAML_TIMESTAMP_REGEXP = new RegExp(
|
||||
'^([0-9][0-9][0-9][0-9])' + // [1] year
|
||||
'-([0-9][0-9]?)' + // [2] month
|
||||
'-([0-9][0-9]?)' + // [3] day
|
||||
'(?:(?:[Tt]|[ \\t]+)' + // ...
|
||||
'([0-9][0-9]?)' + // [4] hour
|
||||
':([0-9][0-9])' + // [5] minute
|
||||
':([0-9][0-9])' + // [6] second
|
||||
'(?:\\.([0-9]*))?' + // [7] fraction
|
||||
'(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour
|
||||
'(?::([0-9][0-9]))?))?)?$'); // [11] tz_minute
|
||||
|
||||
function resolveYamlTimestamp(data) {
|
||||
if (null === data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var match, year, month, day, hour, minute, second, fraction = 0,
|
||||
delta = null, tz_hour, tz_minute, date;
|
||||
|
||||
match = YAML_TIMESTAMP_REGEXP.exec(data);
|
||||
|
||||
if (null === match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function constructYamlTimestamp(data) {
|
||||
var match, year, month, day, hour, minute, second, fraction: number | string = 0,
|
||||
delta = null, tz_hour, tz_minute, date;
|
||||
|
||||
match = YAML_TIMESTAMP_REGEXP.exec(data);
|
||||
|
||||
if (null === match) {
|
||||
throw new Error('Date resolve error');
|
||||
}
|
||||
|
||||
// match: [1] year [2] month [3] day
|
||||
|
||||
year = +(match[1]);
|
||||
month = +(match[2]) - 1; // JS month starts with 0
|
||||
day = +(match[3]);
|
||||
|
||||
if (!match[4]) { // no hour
|
||||
return new Date(Date.UTC(year, month, day));
|
||||
}
|
||||
|
||||
// match: [4] hour [5] minute [6] second [7] fraction
|
||||
|
||||
hour = +(match[4]);
|
||||
minute = +(match[5]);
|
||||
second = +(match[6]);
|
||||
|
||||
if (match[7]) {
|
||||
fraction = match[7].slice(0, 3);
|
||||
while ((<any>fraction).length < 3) { // milli-seconds
|
||||
fraction = fraction + '0';
|
||||
}
|
||||
fraction = +fraction;
|
||||
}
|
||||
|
||||
// match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute
|
||||
|
||||
if (match[9]) {
|
||||
tz_hour = +(match[10]);
|
||||
tz_minute = +(match[11] || 0);
|
||||
delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds
|
||||
if ('-' === match[9]) {
|
||||
delta = -delta;
|
||||
}
|
||||
}
|
||||
|
||||
date = new Date(Date.UTC(year, month, day, hour, minute, second, <number>fraction));
|
||||
|
||||
if (delta) {
|
||||
date.setTime(date.getTime() - delta);
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
function representYamlTimestamp(object /*, style*/) {
|
||||
return object.toISOString();
|
||||
}
|
||||
|
||||
export default new Type('tag:yaml.org,2002:timestamp', {
|
||||
kind: 'scalar',
|
||||
resolve: resolveYamlTimestamp,
|
||||
construct: constructYamlTimestamp,
|
||||
instanceOf: Date,
|
||||
represent: representYamlTimestamp
|
||||
});
|
||||
125
src/yaml-ast-parser/yamlAST.ts
Normal file
125
src/yaml-ast-parser/yamlAST.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
|
||||
/**
|
||||
* Created by kor on 06/05/15.
|
||||
*/
|
||||
import YAMLException from './exception';
|
||||
export enum Kind {
|
||||
SCALAR,
|
||||
MAPPING,
|
||||
MAP,
|
||||
SEQ,
|
||||
ANCHOR_REF,
|
||||
INCLUDE_REF
|
||||
}
|
||||
|
||||
export interface YAMLDocument {
|
||||
startPosition: number
|
||||
endPosition: number
|
||||
errors: YAMLException[]
|
||||
}
|
||||
export interface YAMLNode extends YAMLDocument {
|
||||
startPosition: number
|
||||
endPosition: number
|
||||
kind: Kind
|
||||
anchorId?: string
|
||||
valueObject?: any
|
||||
parent: YAMLNode
|
||||
errors: YAMLException[]
|
||||
/**
|
||||
* @deprecated Inspect kind and cast to the appropriate subtype instead.
|
||||
*/
|
||||
value?: any
|
||||
|
||||
/**
|
||||
* @deprecated Inspect kind and cast to the appropriate subtype instead.
|
||||
*/
|
||||
key?: any
|
||||
|
||||
/**
|
||||
* @deprecated Inspect kind and cast to the appropriate subtype instead.
|
||||
*/
|
||||
mappings?: any
|
||||
}
|
||||
|
||||
export interface YAMLAnchorReference extends YAMLNode {
|
||||
referencesAnchor: string
|
||||
value: YAMLNode
|
||||
}
|
||||
export interface YAMLScalar extends YAMLNode {
|
||||
value: string
|
||||
doubleQuoted?: boolean
|
||||
singleQuoted?: boolean
|
||||
plainScalar?: boolean
|
||||
rawValue: string
|
||||
}
|
||||
|
||||
export interface YAMLMapping extends YAMLNode {
|
||||
key: YAMLScalar
|
||||
value: YAMLNode
|
||||
}
|
||||
export interface YAMLSequence extends YAMLNode {
|
||||
items: YAMLNode[]
|
||||
}
|
||||
export interface YamlMap extends YAMLNode {
|
||||
mappings: YAMLMapping[]
|
||||
}
|
||||
export function newMapping(key: YAMLScalar, value: YAMLNode): YAMLMapping {
|
||||
var end = (value ? value.endPosition : key.endPosition + 1); //FIXME.workaround, end should be defied by position of ':'
|
||||
//console.log('key: ' + key.value + ' ' + key.startPosition + '..' + key.endPosition + ' ' + value + ' end: ' + end);
|
||||
var node = {
|
||||
key: key,
|
||||
value: value,
|
||||
startPosition: key.startPosition,
|
||||
endPosition: end,
|
||||
kind: Kind.MAPPING,
|
||||
parent: null,
|
||||
errors: []
|
||||
};
|
||||
return node
|
||||
}
|
||||
export function newAnchorRef(key: string, start: number, end: number, value: YAMLNode): YAMLAnchorReference {
|
||||
return {
|
||||
errors: [],
|
||||
referencesAnchor: key,
|
||||
value: value,
|
||||
startPosition: start,
|
||||
endPosition: end,
|
||||
kind: Kind.ANCHOR_REF,
|
||||
parent: null
|
||||
}
|
||||
}
|
||||
export function newScalar(v: string = ""): YAMLScalar {
|
||||
return {
|
||||
errors: [],
|
||||
startPosition: -1,
|
||||
endPosition: -1,
|
||||
value: v,
|
||||
kind: Kind.SCALAR,
|
||||
parent: null,
|
||||
doubleQuoted: false,
|
||||
rawValue: v
|
||||
}
|
||||
}
|
||||
export function newItems(): YAMLSequence {
|
||||
return {
|
||||
errors: [],
|
||||
startPosition: -1,
|
||||
endPosition: -1,
|
||||
items: [],
|
||||
kind: Kind.SEQ,
|
||||
parent: null
|
||||
}
|
||||
}
|
||||
export function newSeq(): YAMLSequence {
|
||||
return newItems();
|
||||
}
|
||||
export function newMap(mappings?: YAMLMapping[]): YamlMap {
|
||||
return {
|
||||
errors: [],
|
||||
startPosition: -1,
|
||||
endPosition: -1,
|
||||
mappings: mappings ? mappings : [],
|
||||
kind: Kind.MAP,
|
||||
parent: null
|
||||
}
|
||||
}
|
||||
25
src/yaml-languageservice/jsonContributions.ts
Normal file
25
src/yaml-languageservice/jsonContributions.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import {Thenable, MarkedString, CompletionItem} from 'vscode-json-languageservice';
|
||||
|
||||
export interface JSONWorkerContribution {
|
||||
getInfoContribution(uri: string, location: JSONPath): Thenable<MarkedString[]>;
|
||||
collectPropertyCompletions(uri: string, location: JSONPath, currentWord: string, addValue: boolean, isLast: boolean, result: CompletionsCollector): Thenable<any>;
|
||||
collectValueCompletions(uri: string, location: JSONPath, propertyKey: string, result: CompletionsCollector): Thenable<any>;
|
||||
collectDefaultCompletions(uri: string, result: CompletionsCollector): Thenable<any>;
|
||||
resolveCompletion?(item: CompletionItem): Thenable<CompletionItem>;
|
||||
}
|
||||
export type Segment = string | number;
|
||||
export type JSONPath = Segment[];
|
||||
|
||||
export interface CompletionsCollector {
|
||||
add(suggestion: CompletionItem): void;
|
||||
error(message: string): void;
|
||||
log(message: string): void;
|
||||
setAsIncomplete(): void;
|
||||
getNumberOfProposals(): number;
|
||||
}
|
||||
51
src/yaml-languageservice/jsonSchema.ts
Normal file
51
src/yaml-languageservice/jsonSchema.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface JSONSchema {
|
||||
id?: string;
|
||||
$schema?: string;
|
||||
type?: string | string[];
|
||||
title?: string;
|
||||
default?: any;
|
||||
definitions?: JSONSchemaMap;
|
||||
description?: string;
|
||||
properties?: JSONSchemaMap;
|
||||
patternProperties?: JSONSchemaMap;
|
||||
additionalProperties?: any;
|
||||
minProperties?: number;
|
||||
maxProperties?: number;
|
||||
dependencies?: JSONSchemaMap | string[];
|
||||
items?: any;
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
uniqueItems?: boolean;
|
||||
additionalItems?: boolean;
|
||||
pattern?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
exclusiveMinimum?: boolean;
|
||||
exclusiveMaximum?: boolean;
|
||||
multipleOf?: number;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
anyOf?: JSONSchema[];
|
||||
allOf?: JSONSchema[];
|
||||
oneOf?: JSONSchema[];
|
||||
not?: JSONSchema;
|
||||
enum?: any[];
|
||||
format?: string;
|
||||
errorMessage?: string; // VSCode extension
|
||||
patternErrorMessage?: string; // VSCode extension
|
||||
deprecationMessage?: string; // VSCode extension
|
||||
enumDescriptions?: string[]; // VSCode extension
|
||||
"x-kubernetes-group-version-kind"?; //Kubernetes extension
|
||||
}
|
||||
|
||||
export interface JSONSchemaMap {
|
||||
[name: string]:JSONSchema;
|
||||
}
|
||||
1050
src/yaml-languageservice/parser/jsonParser.ts
Normal file
1050
src/yaml-languageservice/parser/jsonParser.ts
Normal file
File diff suppressed because it is too large
Load diff
242
src/yaml-languageservice/parser/yamlParser.ts
Normal file
242
src/yaml-languageservice/parser/yamlParser.ts
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
'use strict';
|
||||
|
||||
import { JSONSchema } from 'vscode-json-languageservice/lib/jsonSchema';
|
||||
import { ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IApplicableSchema, JSONDocument } from './jsonParser';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as Yaml from '../../yaml-ast-parser/index'
|
||||
import { Kind } from '../../yaml-ast-parser/index'
|
||||
|
||||
import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator'
|
||||
|
||||
export class SingleYAMLDocument extends JSONDocument {
|
||||
private lines;
|
||||
public root;
|
||||
public errors;
|
||||
public warnings;
|
||||
|
||||
constructor(lines: number[]) {
|
||||
super(null, []);
|
||||
this.lines = lines;
|
||||
this.root = null;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
public getSchemas(schema, doc, node) {
|
||||
let matchingSchemas = [];
|
||||
doc.validate(schema, matchingSchemas, node.start);
|
||||
return matchingSchemas;
|
||||
}
|
||||
|
||||
public getNodeFromOffset(offset: number): ASTNode {
|
||||
return this.getNodeFromOffsetEndInclusive(offset);
|
||||
}
|
||||
|
||||
private getNodeByIndent = (lines: number[], offset: number, node: ASTNode) => {
|
||||
|
||||
const { line, column: indent } = getPosition(offset, this.lines)
|
||||
|
||||
const children = node.getChildNodes()
|
||||
|
||||
function findNode(children) {
|
||||
for (var idx = 0; idx < children.length; idx++) {
|
||||
var child = children[idx];
|
||||
|
||||
const { line: childLine, column: childCol } = getPosition(child.start, lines);
|
||||
|
||||
if (childCol > indent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newChildren = child.getChildNodes()
|
||||
const foundNode = findNode(newChildren)
|
||||
|
||||
if (foundNode) {
|
||||
return foundNode;
|
||||
}
|
||||
|
||||
// We have the right indentation, need to return based on line
|
||||
if (childLine == line) {
|
||||
return child;
|
||||
}
|
||||
if (childLine > line) {
|
||||
// Get previous
|
||||
(idx - 1) >= 0 ? children[idx - 1] : child;
|
||||
}
|
||||
// Else continue loop to try next element
|
||||
}
|
||||
|
||||
// Special case, we found the correct
|
||||
return children[children.length - 1]
|
||||
}
|
||||
|
||||
return findNode(children) || node
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
||||
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.kind) {
|
||||
case Yaml.Kind.MAP: {
|
||||
const instance = <Yaml.YamlMap>node;
|
||||
|
||||
const result = new ObjectASTNode(parent, null, node.startPosition, node.endPosition)
|
||||
result.addProperty
|
||||
|
||||
for (const mapping of instance.mappings) {
|
||||
result.addProperty(<PropertyASTNode>recursivelyBuildAst(result, mapping))
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.MAPPING: {
|
||||
const instance = <Yaml.YAMLMapping>node;
|
||||
const key = instance.key;
|
||||
|
||||
// Technically, this is an arbitrary node in YAML
|
||||
// I doubt we would get a better string representation by parsing it
|
||||
const keyNode = new StringASTNode(null, null, true, key.startPosition, key.endPosition);
|
||||
keyNode.value = key.value;
|
||||
|
||||
const result = new PropertyASTNode(parent, keyNode)
|
||||
result.end = instance.endPosition
|
||||
|
||||
const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNode(parent, key.value, instance.endPosition, instance.endPosition)
|
||||
valueNode.location = key.value
|
||||
|
||||
result.setValue(valueNode)
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.SEQ: {
|
||||
const instance = <Yaml.YAMLSequence>node;
|
||||
|
||||
const result = new ArrayASTNode(parent, null, instance.startPosition, instance.endPosition);
|
||||
|
||||
let count = 0;
|
||||
for (const item of instance.items) {
|
||||
if (item === null && count === instance.items.length - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Be aware of https://github.com/nodeca/js-yaml/issues/321
|
||||
// Cannot simply work around it here because we need to know if we are in Flow or Block
|
||||
var itemNode = (item === null) ? new NullASTNode(parent, null, instance.endPosition, instance.endPosition) : recursivelyBuildAst(result, item);
|
||||
|
||||
itemNode.location = count++;
|
||||
result.addItem(itemNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
case Yaml.Kind.SCALAR: {
|
||||
const instance = <Yaml.YAMLScalar>node;
|
||||
const type = Yaml.determineScalarType(instance)
|
||||
|
||||
// The name is set either by the sequence or the mapping case.
|
||||
const name = null;
|
||||
const value = instance.value;
|
||||
|
||||
//This is a patch for redirecting values with these strings to be boolean nodes because its not supported in the parser.
|
||||
let possibleBooleanValues = ['y', 'Y', 'yes', 'Yes', 'YES', 'n', 'N', 'no', 'No', 'NO', 'on', 'On', 'ON', 'off', 'Off', 'OFF'];
|
||||
if (possibleBooleanValues.indexOf(value.toString()) !== -1) {
|
||||
return new BooleanASTNode(parent, name, value, node.startPosition, node.endPosition)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Yaml.ScalarType.null: {
|
||||
return new NullASTNode(parent, name, instance.startPosition, instance.endPosition);
|
||||
}
|
||||
case Yaml.ScalarType.bool: {
|
||||
return new BooleanASTNode(parent, name, Yaml.parseYamlBoolean(value), node.startPosition, node.endPosition)
|
||||
}
|
||||
case Yaml.ScalarType.int: {
|
||||
const result = new NumberASTNode(parent, name, node.startPosition, node.endPosition);
|
||||
result.value = Yaml.parseYamlInteger(value);
|
||||
result.isInteger = true;
|
||||
return result;
|
||||
}
|
||||
case Yaml.ScalarType.float: {
|
||||
const result = new NumberASTNode(parent, name, node.startPosition, node.endPosition);
|
||||
result.value = Yaml.parseYamlFloat(value);
|
||||
result.isInteger = false;
|
||||
return result;
|
||||
}
|
||||
case Yaml.ScalarType.string: {
|
||||
const result = new StringASTNode(parent, name, false, node.startPosition, node.endPosition);
|
||||
result.value = node.value;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Yaml.Kind.ANCHOR_REF: {
|
||||
const instance = (<Yaml.YAMLAnchorReference>node).value
|
||||
|
||||
return recursivelyBuildAst(parent, instance) ||
|
||||
new NullASTNode(parent, null, node.startPosition, node.endPosition);
|
||||
}
|
||||
case Yaml.Kind.INCLUDE_REF: {
|
||||
const result = new StringASTNode(parent, null, false, node.startPosition, node.endPosition);
|
||||
result.value = node.value;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertError(e: Yaml.YAMLException) {
|
||||
return { message: `${e.reason}`, location: { start: e.mark.position, end: e.mark.position + e.mark.column, code: ErrorCode.Undefined } }
|
||||
}
|
||||
|
||||
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) {
|
||||
let _doc = new SingleYAMLDocument(startPositions);
|
||||
_doc.root = recursivelyBuildAst(null, yamlDoc)
|
||||
|
||||
if (!_doc.root) {
|
||||
// TODO: When this is true, consider not pushing the other errors.
|
||||
_doc.errors.push({ message: localize('Invalid symbol', 'Expected a YAML object, array or literal'), code: ErrorCode.Undefined, location: { start: yamlDoc.startPosition, end: yamlDoc.endPosition } });
|
||||
}
|
||||
|
||||
const duplicateKeyReason = 'duplicate key'
|
||||
|
||||
const errors = yamlDoc.errors.filter(e => e.reason !== duplicateKeyReason && !e.isWarning).map(e => convertError(e))
|
||||
const warnings = yamlDoc.errors.filter(e => e.reason === duplicateKeyReason || e.isWarning).map(e => convertError(e))
|
||||
|
||||
errors.forEach(e => _doc.errors.push(e));
|
||||
warnings.forEach(e => _doc.warnings.push(e));
|
||||
|
||||
return _doc;
|
||||
}
|
||||
|
||||
export class YAMLDocument {
|
||||
public documents: JSONDocument[]
|
||||
private errors;
|
||||
private warnings;
|
||||
|
||||
constructor(documents: JSONDocument[]) {
|
||||
this.documents = documents;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function parse(text: string): YAMLDocument {
|
||||
|
||||
const startPositions = getLineStartPositions(text)
|
||||
// This is documented to return a YAMLNode even though the
|
||||
// typing only returns a YAMLDocument
|
||||
const yamlDocs = []
|
||||
Yaml.loadAll(text, doc => yamlDocs.push(doc), {})
|
||||
|
||||
return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions)));
|
||||
}
|
||||
72
src/yaml-languageservice/services/documentSymbols.ts
Normal file
72
src/yaml-languageservice/services/documentSymbols.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Parser = require('../parser/jsonParser');
|
||||
import Strings = require('../utils/strings');
|
||||
|
||||
import { SymbolInformation, SymbolKind, TextDocument, Range, Location } from 'vscode-languageserver-types';
|
||||
import { Thenable } from "../yamlLanguageService";
|
||||
import { IJSONSchemaService } from "./jsonSchemaService";
|
||||
|
||||
export class YAMLDocumentSymbols {
|
||||
|
||||
public findDocumentSymbols(document: TextDocument, doc: Parser.JSONDocument): SymbolInformation[] {
|
||||
|
||||
if(!doc || doc["documents"].length === 0){
|
||||
return null;
|
||||
}
|
||||
|
||||
let collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => {
|
||||
if (node.type === 'array') {
|
||||
(<Parser.ArrayASTNode>node).items.forEach((node: Parser.ASTNode) => {
|
||||
collectOutlineEntries(result, node, containerName);
|
||||
});
|
||||
} else if (node.type === 'object') {
|
||||
let objectNode = <Parser.ObjectASTNode>node;
|
||||
|
||||
objectNode.properties.forEach((property: Parser.PropertyASTNode) => {
|
||||
let location = Location.create(document.uri, Range.create(document.positionAt(property.start), document.positionAt(property.end)));
|
||||
let valueNode = property.value;
|
||||
if (valueNode) {
|
||||
let childContainerName = containerName ? containerName + '.' + property.key.value : property.key.value;
|
||||
result.push({ name: property.key.getValue(), kind: this.getSymbolKind(valueNode.type), location: location, containerName: containerName });
|
||||
collectOutlineEntries(result, valueNode, childContainerName);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
let results = [];
|
||||
for(let yamlDoc in doc["documents"]){
|
||||
let currentYAMLDoc = doc["documents"][yamlDoc];
|
||||
if(currentYAMLDoc.root){
|
||||
let result = collectOutlineEntries([], currentYAMLDoc.root, void 0);
|
||||
results = results.concat(result);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private getSymbolKind(nodeType: string): SymbolKind {
|
||||
switch (nodeType) {
|
||||
case 'object':
|
||||
return SymbolKind.Module;
|
||||
case 'string':
|
||||
return SymbolKind.String;
|
||||
case 'number':
|
||||
return SymbolKind.Number;
|
||||
case 'array':
|
||||
return SymbolKind.Array;
|
||||
case 'boolean':
|
||||
return SymbolKind.Boolean;
|
||||
default: // 'null'
|
||||
return SymbolKind.Variable;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
534
src/yaml-languageservice/services/jsonSchemaService.ts
Normal file
534
src/yaml-languageservice/services/jsonSchemaService.ts
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Json = require('jsonc-parser');
|
||||
import {JSONSchema, JSONSchemaMap} from '../jsonSchema';
|
||||
import URI from 'vscode-uri';
|
||||
import Strings = require('../utils/strings');
|
||||
import Parser = require('../parser/jsonParser');
|
||||
import {SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable} from '../yamlLanguageService';
|
||||
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface IJSONSchemaService {
|
||||
|
||||
/**
|
||||
* Registers a schema file in the current workspace to be applicable to files that match the pattern
|
||||
*/
|
||||
registerExternalSchema(uri: string, filePatterns?: string[], unresolvedSchema?: JSONSchema): ISchemaHandle;
|
||||
|
||||
/**
|
||||
* Clears all cached schema files
|
||||
*/
|
||||
clearExternalSchemas(): void;
|
||||
|
||||
/**
|
||||
* Registers contributed schemas
|
||||
*/
|
||||
setSchemaContributions(schemaContributions: ISchemaContributions): void;
|
||||
|
||||
/**
|
||||
* Looks up the appropriate schema for the given URI
|
||||
*/
|
||||
getSchemaForResource(resource: string): Thenable<ResolvedSchema>;
|
||||
|
||||
/**
|
||||
* Returns all registered schema ids
|
||||
*/
|
||||
getRegisteredSchemaIds(filter?: (scheme) => boolean): string[];
|
||||
}
|
||||
|
||||
export interface ISchemaAssociations {
|
||||
[pattern: string]: string[];
|
||||
}
|
||||
|
||||
export interface ISchemaContributions {
|
||||
schemas?: { [id: string]: JSONSchema };
|
||||
schemaAssociations?: ISchemaAssociations;
|
||||
}
|
||||
|
||||
export declare type CustomSchemaProvider = (uri: string) => Thenable<string>;
|
||||
|
||||
export interface ISchemaHandle {
|
||||
/**
|
||||
* The schema id
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* The schema from the file, with potential $ref references
|
||||
*/
|
||||
getUnresolvedSchema(): Thenable<UnresolvedSchema>;
|
||||
|
||||
/**
|
||||
* The schema from the file, with references resolved
|
||||
*/
|
||||
getResolvedSchema(): Thenable<ResolvedSchema>;
|
||||
}
|
||||
|
||||
|
||||
export class FilePatternAssociation {
|
||||
|
||||
private schemas: string[];
|
||||
private combinedSchemaId: string;
|
||||
private patternRegExp: RegExp;
|
||||
private combinedSchema: ISchemaHandle;
|
||||
|
||||
constructor(pattern: string) {
|
||||
this.combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(pattern);
|
||||
try {
|
||||
this.patternRegExp = new RegExp(Strings.convertSimple2RegExpPattern(pattern) + '$');
|
||||
} catch (e) {
|
||||
// invalid pattern
|
||||
this.patternRegExp = null;
|
||||
}
|
||||
this.schemas = [];
|
||||
this.combinedSchema = null;
|
||||
}
|
||||
|
||||
public addSchema(id: string) {
|
||||
this.schemas.push(id);
|
||||
this.combinedSchema = null;
|
||||
}
|
||||
|
||||
public matchesPattern(fileName: string): boolean {
|
||||
return this.patternRegExp && this.patternRegExp.test(fileName);
|
||||
}
|
||||
|
||||
public getCombinedSchema(service: JSONSchemaService): ISchemaHandle {
|
||||
if (!this.combinedSchema) {
|
||||
this.combinedSchema = service.createCombinedSchema(this.combinedSchemaId, this.schemas);
|
||||
}
|
||||
return this.combinedSchema;
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaHandle implements ISchemaHandle {
|
||||
|
||||
public url: string;
|
||||
|
||||
private resolvedSchema: Thenable<ResolvedSchema>;
|
||||
private unresolvedSchema: Thenable<UnresolvedSchema>;
|
||||
private service: JSONSchemaService;
|
||||
|
||||
constructor(service: JSONSchemaService, url: string, unresolvedSchemaContent?: JSONSchema) {
|
||||
this.service = service;
|
||||
this.url = url;
|
||||
if (unresolvedSchemaContent) {
|
||||
this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
|
||||
}
|
||||
}
|
||||
|
||||
public getUnresolvedSchema(): Thenable<UnresolvedSchema> {
|
||||
if (!this.unresolvedSchema) {
|
||||
this.unresolvedSchema = this.service.loadSchema(this.url);
|
||||
}
|
||||
return this.unresolvedSchema;
|
||||
}
|
||||
|
||||
public getResolvedSchema(): Thenable<ResolvedSchema> {
|
||||
if (!this.resolvedSchema) {
|
||||
this.resolvedSchema = this.getUnresolvedSchema().then(unresolved => {
|
||||
return this.service.resolveSchemaContent(unresolved, this.url);
|
||||
});
|
||||
}
|
||||
return this.resolvedSchema;
|
||||
}
|
||||
|
||||
public clearSchema(): void {
|
||||
this.resolvedSchema = null;
|
||||
this.unresolvedSchema = null;
|
||||
}
|
||||
}
|
||||
|
||||
export class UnresolvedSchema {
|
||||
public schema: JSONSchema;
|
||||
public errors: string[];
|
||||
|
||||
constructor(schema: JSONSchema, errors: string[] = []) {
|
||||
this.schema = schema;
|
||||
this.errors = errors;
|
||||
}
|
||||
}
|
||||
|
||||
export class ResolvedSchema {
|
||||
public schema: JSONSchema;
|
||||
public errors: string[];
|
||||
|
||||
constructor(schema: JSONSchema, errors: string[] = []) {
|
||||
this.schema = schema;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public getSection(path: string[]): JSONSchema {
|
||||
return this.getSectionRecursive(path, this.schema);
|
||||
}
|
||||
|
||||
private getSectionRecursive(path: string[], schema: JSONSchema): JSONSchema {
|
||||
if (!schema || path.length === 0) {
|
||||
return schema;
|
||||
}
|
||||
let next = path.shift();
|
||||
|
||||
if (schema.properties && schema.properties[next]) {
|
||||
return this.getSectionRecursive(path, schema.properties[next]);
|
||||
} else if (schema.patternProperties) {
|
||||
Object.keys(schema.patternProperties).forEach((pattern) => {
|
||||
let regex = new RegExp(pattern);
|
||||
if (regex.test(next)) {
|
||||
return this.getSectionRecursive(path, schema.patternProperties[pattern]);
|
||||
}
|
||||
});
|
||||
} else if (schema.additionalProperties) {
|
||||
return this.getSectionRecursive(path, schema.additionalProperties);
|
||||
} else if (next.match('[0-9]+')) {
|
||||
if (schema.items) {
|
||||
return this.getSectionRecursive(path, schema.items);
|
||||
} else if (Array.isArray(schema.items)) {
|
||||
try {
|
||||
let index = parseInt(next, 10);
|
||||
if (schema.items[index]) {
|
||||
return this.getSectionRecursive(path, schema.items[index]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class JSONSchemaService implements IJSONSchemaService {
|
||||
|
||||
private contributionSchemas: { [id: string]: SchemaHandle };
|
||||
private contributionAssociations: { [id: string]: string[] };
|
||||
|
||||
private schemasById: { [id: string]: SchemaHandle };
|
||||
private filePatternAssociations: FilePatternAssociation[];
|
||||
private filePatternAssociationById: { [id: string]: FilePatternAssociation };
|
||||
private registeredSchemasIds: { [id: string]: boolean };
|
||||
|
||||
private contextService: WorkspaceContextService;
|
||||
private callOnDispose: Function[];
|
||||
private requestService: SchemaRequestService;
|
||||
private promiseConstructor: PromiseConstructor;
|
||||
private customSchemaProvider: CustomSchemaProvider;
|
||||
|
||||
constructor(requestService: SchemaRequestService, contextService?: WorkspaceContextService, customSchemaProvider?: CustomSchemaProvider, promiseConstructor?: PromiseConstructor) {
|
||||
this.contextService = contextService;
|
||||
this.requestService = requestService;
|
||||
this.promiseConstructor = promiseConstructor || Promise;
|
||||
this.callOnDispose = [];
|
||||
this.customSchemaProvider = customSchemaProvider;
|
||||
this.contributionSchemas = {};
|
||||
this.contributionAssociations = {};
|
||||
this.schemasById = {};
|
||||
this.filePatternAssociations = [];
|
||||
this.filePatternAssociationById = {};
|
||||
this.registeredSchemasIds = {};
|
||||
}
|
||||
|
||||
public getRegisteredSchemaIds(filter?: (scheme) => boolean): string[] {
|
||||
return Object.keys(this.registeredSchemasIds).filter(id => {
|
||||
let scheme = URI.parse(id).scheme;
|
||||
return scheme !== 'schemaservice' && (!filter || filter(scheme));
|
||||
});
|
||||
}
|
||||
|
||||
public get promise() {
|
||||
return this.promiseConstructor;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
while (this.callOnDispose.length > 0) {
|
||||
this.callOnDispose.pop()();
|
||||
}
|
||||
}
|
||||
|
||||
public onResourceChange(uri: string): boolean {
|
||||
uri = this.normalizeId(uri);
|
||||
let schemaFile = this.schemasById[uri];
|
||||
if (schemaFile) {
|
||||
schemaFile.clearSchema();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private normalizeId(id: string) {
|
||||
// remove trailing '#', normalize drive capitalization
|
||||
return URI.parse(id).toString();
|
||||
}
|
||||
|
||||
public setSchemaContributions(schemaContributions: ISchemaContributions): void {
|
||||
if (schemaContributions.schemas) {
|
||||
let schemas = schemaContributions.schemas;
|
||||
for (let id in schemas) {
|
||||
let normalizedId = this.normalizeId(id);
|
||||
this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
|
||||
}
|
||||
}
|
||||
if (schemaContributions.schemaAssociations) {
|
||||
let schemaAssociations = schemaContributions.schemaAssociations;
|
||||
for (let pattern in schemaAssociations) {
|
||||
let associations = schemaAssociations[pattern];
|
||||
this.contributionAssociations[pattern] = associations;
|
||||
|
||||
var fpa = this.getOrAddFilePatternAssociation(pattern);
|
||||
associations.forEach(schemaId => {
|
||||
let id = this.normalizeId(schemaId);
|
||||
fpa.addSchema(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addSchemaHandle(id: string, unresolvedSchemaContent?: JSONSchema): SchemaHandle {
|
||||
let schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
|
||||
this.schemasById[id] = schemaHandle;
|
||||
return schemaHandle;
|
||||
}
|
||||
|
||||
private getOrAddSchemaHandle(id: string, unresolvedSchemaContent?: JSONSchema): ISchemaHandle {
|
||||
return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
|
||||
}
|
||||
|
||||
private getOrAddFilePatternAssociation(pattern: string) {
|
||||
let fpa = this.filePatternAssociationById[pattern];
|
||||
if (!fpa) {
|
||||
fpa = new FilePatternAssociation(pattern);
|
||||
this.filePatternAssociationById[pattern] = fpa;
|
||||
this.filePatternAssociations.push(fpa);
|
||||
}
|
||||
return fpa;
|
||||
}
|
||||
|
||||
public registerExternalSchema(uri: string, filePatterns: string[] = null, unresolvedSchemaContent?: JSONSchema): ISchemaHandle {
|
||||
let id = this.normalizeId(uri);
|
||||
this.registeredSchemasIds[id] = true;
|
||||
|
||||
if (filePatterns) {
|
||||
filePatterns.forEach(pattern => {
|
||||
this.getOrAddFilePatternAssociation(pattern).addSchema(id);
|
||||
});
|
||||
}
|
||||
return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
|
||||
}
|
||||
|
||||
public clearExternalSchemas(): void {
|
||||
this.schemasById = {};
|
||||
this.filePatternAssociations = [];
|
||||
this.filePatternAssociationById = {};
|
||||
this.registeredSchemasIds = {};
|
||||
|
||||
for (let id in this.contributionSchemas) {
|
||||
this.schemasById[id] = this.contributionSchemas[id];
|
||||
this.registeredSchemasIds[id] = true;
|
||||
}
|
||||
for (let pattern in this.contributionAssociations) {
|
||||
var fpa = this.getOrAddFilePatternAssociation(pattern);
|
||||
|
||||
this.contributionAssociations[pattern].forEach(schemaId => {
|
||||
let id = this.normalizeId(schemaId);
|
||||
fpa.addSchema(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getResolvedSchema(schemaId: string): Thenable<ResolvedSchema> {
|
||||
let id = this.normalizeId(schemaId);
|
||||
let schemaHandle = this.schemasById[id];
|
||||
if (schemaHandle) {
|
||||
return schemaHandle.getResolvedSchema();
|
||||
}
|
||||
return this.promise.resolve(null);
|
||||
}
|
||||
|
||||
public loadSchema(url: string): Thenable<UnresolvedSchema> {
|
||||
if (!this.requestService) {
|
||||
let errorMessage = localize('json.schema.norequestservice', 'Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
|
||||
return this.promise.resolve(new UnresolvedSchema(<JSONSchema>{}, [errorMessage]));
|
||||
}
|
||||
return this.requestService(url).then(
|
||||
content => {
|
||||
if (!content) {
|
||||
let errorMessage = localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
|
||||
return new UnresolvedSchema(<JSONSchema>{}, [errorMessage]);
|
||||
}
|
||||
|
||||
let schemaContent: JSONSchema = {};
|
||||
let jsonErrors = [];
|
||||
schemaContent = Json.parse(content, jsonErrors);
|
||||
let errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), Json.getParseErrorMessage(jsonErrors[0]))] : [];
|
||||
return new UnresolvedSchema(schemaContent, errors);
|
||||
},
|
||||
(error: any) => {
|
||||
let errorMessage = localize('json.schema.unabletoload', 'Unable to load schema from \'{0}\': {1}', toDisplayString(url), error.toString());
|
||||
return new UnresolvedSchema(<JSONSchema>{}, [errorMessage]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public resolveSchemaContent(schemaToResolve: UnresolvedSchema, schemaURL: string): Thenable<ResolvedSchema> {
|
||||
|
||||
let resolveErrors: string[] = schemaToResolve.errors.slice(0);
|
||||
let schema = schemaToResolve.schema;
|
||||
let contextService = this.contextService;
|
||||
|
||||
let findSection = (schema: JSONSchema, path: string): any => {
|
||||
if (!path) {
|
||||
return schema;
|
||||
}
|
||||
let current: any = schema;
|
||||
if (path[0] === '/') {
|
||||
path = path.substr(1);
|
||||
}
|
||||
path.split('/').some((part) => {
|
||||
current = current[part];
|
||||
return !current;
|
||||
});
|
||||
return current;
|
||||
};
|
||||
|
||||
let resolveLink = (node: any, linkedSchema: JSONSchema, linkPath: string): void => {
|
||||
let section = findSection(linkedSchema, linkPath);
|
||||
if (section) {
|
||||
for (let key in section) {
|
||||
if (section.hasOwnProperty(key) && !node.hasOwnProperty(key)) {
|
||||
node[key] = section[key];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in {1} can not be resolved.', linkPath, linkedSchema.id));
|
||||
}
|
||||
delete node.$ref;
|
||||
};
|
||||
|
||||
let resolveExternalLink = (node: any, uri: string, linkPath: string, parentSchemaURL: string): Thenable<any> => {
|
||||
if (contextService && !/^\w+:\/\/.*/.test(uri)) {
|
||||
uri = contextService.resolveRelativePath(uri, parentSchemaURL);
|
||||
}
|
||||
uri = this.normalizeId(uri);
|
||||
return this.getOrAddSchemaHandle(uri).getUnresolvedSchema().then(unresolvedSchema => {
|
||||
if (unresolvedSchema.errors.length) {
|
||||
let loc = linkPath ? uri + '#' + linkPath : uri;
|
||||
resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
|
||||
}
|
||||
resolveLink(node, unresolvedSchema.schema, linkPath);
|
||||
return resolveRefs(node, unresolvedSchema.schema, uri);
|
||||
});
|
||||
};
|
||||
|
||||
let resolveRefs = (node: JSONSchema, parentSchema: JSONSchema, parentSchemaURL: string): Thenable<any> => {
|
||||
if (!node) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let toWalk: JSONSchema[] = [node];
|
||||
let seen: JSONSchema[] = [];
|
||||
|
||||
let openPromises: Thenable<any>[] = [];
|
||||
|
||||
let collectEntries = (...entries: JSONSchema[]) => {
|
||||
for (let entry of entries) {
|
||||
if (typeof entry === 'object') {
|
||||
toWalk.push(entry);
|
||||
}
|
||||
}
|
||||
};
|
||||
let collectMapEntries = (...maps: JSONSchemaMap[]) => {
|
||||
for (let map of maps) {
|
||||
if (typeof map === 'object') {
|
||||
for (let key in map) {
|
||||
let entry = map[key];
|
||||
toWalk.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let collectArrayEntries = (...arrays: JSONSchema[][]) => {
|
||||
for (let array of arrays) {
|
||||
if (Array.isArray(array)) {
|
||||
toWalk.push.apply(toWalk, array);
|
||||
}
|
||||
}
|
||||
};
|
||||
while (toWalk.length) {
|
||||
let next = toWalk.pop();
|
||||
if (seen.indexOf(next) >= 0) {
|
||||
continue;
|
||||
}
|
||||
seen.push(next);
|
||||
if (next.$ref) {
|
||||
let segments = next.$ref.split('#', 2);
|
||||
if (segments[0].length > 0) {
|
||||
openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL));
|
||||
continue;
|
||||
} else {
|
||||
resolveLink(next, parentSchema, segments[1]);
|
||||
}
|
||||
}
|
||||
collectEntries(next.items, next.additionalProperties, next.not);
|
||||
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies);
|
||||
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items);
|
||||
}
|
||||
return this.promise.all(openPromises);
|
||||
};
|
||||
|
||||
return resolveRefs(schema, schema, schemaURL).then(_ => new ResolvedSchema(schema, resolveErrors));
|
||||
}
|
||||
|
||||
public getSchemaForResource(resource: string ): Thenable<ResolvedSchema> {
|
||||
const resolveSchema = () => {
|
||||
// check for matching file names, last to first
|
||||
for (let i = this.filePatternAssociations.length - 1; i >= 0; i--) {
|
||||
let entry = this.filePatternAssociations[i];
|
||||
if (entry.matchesPattern(resource)) {
|
||||
return entry.getCombinedSchema(this).getResolvedSchema();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
if (this.customSchemaProvider) {
|
||||
return this.customSchemaProvider(resource).then(schemaUri => {
|
||||
return this.loadSchema(schemaUri).then(unsolvedSchema => this.resolveSchemaContent(unsolvedSchema, schemaUri));
|
||||
}).then(schema => schema, err => {
|
||||
return resolveSchema();
|
||||
});
|
||||
} else {
|
||||
return resolveSchema();
|
||||
}
|
||||
}
|
||||
|
||||
public createCombinedSchema(combinedSchemaId: string, schemaIds: string[]): ISchemaHandle {
|
||||
if (schemaIds.length === 1) {
|
||||
return this.getOrAddSchemaHandle(schemaIds[0]);
|
||||
} else {
|
||||
let combinedSchema: JSONSchema = {
|
||||
allOf: schemaIds.map(schemaId => ({ $ref: schemaId }))
|
||||
};
|
||||
return this.addSchemaHandle(combinedSchemaId, combinedSchema);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toDisplayString(url: string) {
|
||||
try {
|
||||
let uri = URI.parse(url);
|
||||
if (uri.scheme === 'file') {
|
||||
return uri.fsPath;
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
return url;
|
||||
}
|
||||
438
src/yaml-languageservice/services/yamlCompletion.ts
Normal file
438
src/yaml-languageservice/services/yamlCompletion.ts
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Red Hat, Inc. All rights reserved.
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import Parser = require('../parser/jsonParser');
|
||||
import Json = require('jsonc-parser');
|
||||
import SchemaService = require('./jsonSchemaService');
|
||||
import { JSONSchema } from '../jsonSchema';
|
||||
import { JSONWorkerContribution, CompletionsCollector } from '../jsonContributions';
|
||||
import { PromiseConstructor, Thenable } from 'vscode-json-languageservice';
|
||||
|
||||
import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Position, Range, TextEdit } from 'vscode-languageserver-types';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { matchOffsetToDocument } from '../utils/arrUtils';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class YAMLCompletion {
|
||||
|
||||
private schemaService: SchemaService.IJSONSchemaService;
|
||||
private contributions: JSONWorkerContribution[];
|
||||
private promise: PromiseConstructor;
|
||||
|
||||
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor?: PromiseConstructor) {
|
||||
this.schemaService = schemaService;
|
||||
this.contributions = contributions;
|
||||
this.promise = promiseConstructor || Promise;
|
||||
}
|
||||
|
||||
public doResolve(item: CompletionItem): Thenable<CompletionItem> {
|
||||
for (let i = this.contributions.length - 1; i >= 0; i--) {
|
||||
if (this.contributions[i].resolveCompletion) {
|
||||
let resolver = this.contributions[i].resolveCompletion(item);
|
||||
if (resolver) {
|
||||
return resolver;
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.promise.resolve(item);
|
||||
}
|
||||
|
||||
public doComplete(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<CompletionList> {
|
||||
|
||||
let result: CompletionList = {
|
||||
items: [],
|
||||
isIncomplete: false
|
||||
};
|
||||
|
||||
let offset = document.offsetAt(position);
|
||||
if(document.getText()[offset] === ":"){
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentDoc = matchOffsetToDocument(offset, doc);
|
||||
if(currentDoc === null){
|
||||
return null;
|
||||
}
|
||||
let node = currentDoc.getNodeFromOffsetEndInclusive(offset);
|
||||
if (this.isInComment(document, node ? node.start : 0, offset)) {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
let currentWord = this.getCurrentWord(document, offset);
|
||||
|
||||
let proposed: { [key: string]: CompletionItem } = {};
|
||||
let collector: CompletionsCollector = {
|
||||
add: (suggestion: CompletionItem) => {
|
||||
let existing = proposed[suggestion.label];
|
||||
if (!existing) {
|
||||
proposed[suggestion.label] = suggestion;
|
||||
result.items.push(suggestion);
|
||||
} else if (!existing.documentation) {
|
||||
existing.documentation = suggestion.documentation;
|
||||
}
|
||||
},
|
||||
setAsIncomplete: () => {
|
||||
result.isIncomplete = true;
|
||||
},
|
||||
error: (message: string) => {
|
||||
console.error(message);
|
||||
},
|
||||
log: (message: string) => {
|
||||
console.log(message);
|
||||
},
|
||||
getNumberOfProposals: () => {
|
||||
return result.items.length;
|
||||
}
|
||||
};
|
||||
|
||||
return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
|
||||
|
||||
if(!schema){
|
||||
return null;
|
||||
}
|
||||
|
||||
let collectionPromises: Thenable<any>[] = [];
|
||||
|
||||
let addValue = true;
|
||||
let currentKey = '';
|
||||
|
||||
let currentProperty: Parser.PropertyASTNode = null;
|
||||
if (node) {
|
||||
|
||||
if (node.type === 'string') {
|
||||
let stringNode = <Parser.StringASTNode>node;
|
||||
if (stringNode.isKey) {
|
||||
addValue = !(node.parent && ((<Parser.PropertyASTNode>node.parent).value));
|
||||
currentProperty = node.parent ? <Parser.PropertyASTNode>node.parent : null;
|
||||
currentKey = document.getText().substring(node.start + 1, node.end - 1);
|
||||
if (node.parent) {
|
||||
node = node.parent.parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proposals for properties
|
||||
if (node && node.type === 'object') {
|
||||
// don't suggest properties that are already present
|
||||
let properties = (<Parser.ObjectASTNode>node).properties;
|
||||
properties.forEach(p => {
|
||||
if (!currentProperty || currentProperty !== p) {
|
||||
proposed[p.key.value] = CompletionItem.create('__');
|
||||
}
|
||||
});
|
||||
|
||||
if (schema) {
|
||||
// property proposals with schema
|
||||
this.getPropertyCompletions(schema, currentDoc, node, addValue, collector);
|
||||
}
|
||||
|
||||
let location = node.getPath();
|
||||
this.contributions.forEach((contribution) => {
|
||||
let collectPromise = contribution.collectPropertyCompletions(document.uri, location, currentWord, addValue, false, collector);
|
||||
if (collectPromise) {
|
||||
collectionPromises.push(collectPromise);
|
||||
}
|
||||
});
|
||||
if ((!schema && currentWord.length > 0 && document.getText().charAt(offset - currentWord.length - 1) !== '"')) {
|
||||
collector.add({
|
||||
kind: CompletionItemKind.Property,
|
||||
label: this.getLabelForValue(currentWord)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// proposals for values
|
||||
let types: { [type: string]: boolean } = {};
|
||||
if (schema) {
|
||||
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types);
|
||||
}
|
||||
if (this.contributions.length > 0) {
|
||||
this.getContributedValueCompletions(currentDoc, node, offset, document, collector, collectionPromises);
|
||||
}
|
||||
|
||||
return this.promise.all(collectionPromises).then(() => {
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getPropertyCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, addValue: boolean, collector: CompletionsCollector): void {
|
||||
let matchingSchemas = doc.getMatchingSchemas(schema.schema);
|
||||
matchingSchemas.forEach((s) => {
|
||||
if (s.node === node && !s.inverted) {
|
||||
let schemaProperties = s.schema.properties;
|
||||
if (schemaProperties) {
|
||||
Object.keys(schemaProperties).forEach((key: string) => {
|
||||
let propertySchema = schemaProperties[key];
|
||||
if (!propertySchema.deprecationMessage && !propertySchema["doNotSuggest"]) {
|
||||
collector.add({
|
||||
kind: CompletionItemKind.Property,
|
||||
label: key,
|
||||
filterText: this.getFilterTextForValue(key),
|
||||
documentation: propertySchema.description || ''
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getValueCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, types: { [type: string]: boolean }): void {
|
||||
let offsetForSeparator = offset;
|
||||
let parentKey: string = null;
|
||||
let valueNode: Parser.ASTNode = null;
|
||||
|
||||
if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean')) {
|
||||
offsetForSeparator = node.end;
|
||||
valueNode = node;
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
if(node && node.type === 'null'){
|
||||
let nodeParent = node.parent;
|
||||
|
||||
/*
|
||||
* This is going to be an object for some reason and we need to find the property
|
||||
* Its an issue with the null node
|
||||
*/
|
||||
if(nodeParent && nodeParent.type === "object"){
|
||||
for(let prop in nodeParent["properties"]){
|
||||
let currNode = nodeParent["properties"][prop];
|
||||
if(currNode.key && currNode.key.location === node.location){
|
||||
node = currNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!node) {
|
||||
this.addSchemaValueCompletions(schema.schema, collector, types);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((node.type === 'property') && offset > (<Parser.PropertyASTNode>node).colonOffset) {
|
||||
let propertyNode = <Parser.PropertyASTNode>node;
|
||||
let valueNode = propertyNode.value;
|
||||
if (valueNode && offset > valueNode.end) {
|
||||
return; // we are past the value node
|
||||
}
|
||||
parentKey = propertyNode.key.value;
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
if (node && (parentKey !== null || node.type === 'array')) {
|
||||
let matchingSchemas = doc.getMatchingSchemas(schema.schema);
|
||||
matchingSchemas.forEach(s => {
|
||||
if (s.node === node && !s.inverted && s.schema) {
|
||||
if (s.schema.items) {
|
||||
if (Array.isArray(s.schema.items)) {
|
||||
let index = this.findItemAtOffset(node, document, offset);
|
||||
if (index < s.schema.items.length) {
|
||||
this.addSchemaValueCompletions(s.schema.items[index], collector, types);
|
||||
}
|
||||
} else {
|
||||
this.addSchemaValueCompletions(s.schema.items, collector, types);
|
||||
}
|
||||
}
|
||||
if (s.schema.properties) {
|
||||
let propertySchema = s.schema.properties[parentKey];
|
||||
if (propertySchema) {
|
||||
this.addSchemaValueCompletions(propertySchema, collector, types);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if(node){
|
||||
if (types['boolean']) {
|
||||
this.addBooleanValueCompletion(true, collector);
|
||||
this.addBooleanValueCompletion(false, collector);
|
||||
}
|
||||
if (types['null']) {
|
||||
this.addNullValueCompletion(collector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getContributedValueCompletions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, collectionPromises: Thenable<any>[]) {
|
||||
if (!node) {
|
||||
this.contributions.forEach((contribution) => {
|
||||
let collectPromise = contribution.collectDefaultCompletions(document.uri, collector);
|
||||
if (collectPromise) {
|
||||
collectionPromises.push(collectPromise);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (node.type === 'string' || node.type === 'number' || node.type === 'boolean' || node.type === 'null') {
|
||||
node = node.parent;
|
||||
}
|
||||
if ((node.type === 'property') && offset > (<Parser.PropertyASTNode>node).colonOffset) {
|
||||
let parentKey = (<Parser.PropertyASTNode>node).key.value;
|
||||
|
||||
let valueNode = (<Parser.PropertyASTNode>node).value;
|
||||
if (!valueNode || offset <= valueNode.end) {
|
||||
let location = node.parent.getPath();
|
||||
this.contributions.forEach((contribution) => {
|
||||
let collectPromise = contribution.collectValueCompletions(document.uri, location, parentKey, collector);
|
||||
if (collectPromise) {
|
||||
collectionPromises.push(collectPromise);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addSchemaValueCompletions(schema: JSONSchema, collector: CompletionsCollector, types: { [type: string]: boolean }): void {
|
||||
this.addDefaultValueCompletions(schema, collector);
|
||||
this.addEnumValueCompletions(schema, collector);
|
||||
this.collectTypes(schema, types);
|
||||
if (Array.isArray(schema.allOf)) {
|
||||
schema.allOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
|
||||
}
|
||||
if (Array.isArray(schema.anyOf)) {
|
||||
schema.anyOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
|
||||
}
|
||||
if (Array.isArray(schema.oneOf)) {
|
||||
schema.oneOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
|
||||
}
|
||||
}
|
||||
|
||||
private addDefaultValueCompletions(schema: JSONSchema, collector: CompletionsCollector, arrayDepth = 0): void {
|
||||
let hasProposals = false;
|
||||
if (schema.default) {
|
||||
let type = schema.type;
|
||||
let value = schema.default;
|
||||
for (let i = arrayDepth; i > 0; i--) {
|
||||
value = [value];
|
||||
type = 'array';
|
||||
}
|
||||
collector.add({
|
||||
kind: this.getSuggestionKind(type),
|
||||
label: this.getLabelForValue(value),
|
||||
detail: localize('json.suggest.default', 'Default value'),
|
||||
});
|
||||
hasProposals = true;
|
||||
}
|
||||
if (!hasProposals && schema.items && !Array.isArray(schema.items)) {
|
||||
this.addDefaultValueCompletions(schema.items, collector, arrayDepth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private addEnumValueCompletions(schema: JSONSchema, collector: CompletionsCollector): void {
|
||||
if (Array.isArray(schema.enum)) {
|
||||
for (let i = 0, length = schema.enum.length; i < length; i++) {
|
||||
let enm = schema.enum[i];
|
||||
let documentation = schema.description;
|
||||
if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
|
||||
documentation = schema.enumDescriptions[i];
|
||||
}
|
||||
collector.add({
|
||||
kind: this.getSuggestionKind(schema.type),
|
||||
label: this.getLabelForValue(enm),
|
||||
documentation
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private collectTypes(schema: JSONSchema, types: { [type: string]: boolean }) {
|
||||
let type = schema.type;
|
||||
if (Array.isArray(type)) {
|
||||
type.forEach(t => types[t] = true);
|
||||
} else {
|
||||
types[type] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private addBooleanValueCompletion(value: boolean, collector: CompletionsCollector): void {
|
||||
collector.add({
|
||||
kind: this.getSuggestionKind('boolean'),
|
||||
label: value ? 'true' : 'false',
|
||||
documentation: ''
|
||||
});
|
||||
}
|
||||
|
||||
private addNullValueCompletion(collector: CompletionsCollector): void {
|
||||
collector.add({
|
||||
kind: this.getSuggestionKind('null'),
|
||||
label: 'null',
|
||||
documentation: ''
|
||||
});
|
||||
}
|
||||
|
||||
private getLabelForValue(value: any): string {
|
||||
let label = typeof value === "string" ? value : JSON.stringify(value);
|
||||
if (label.length > 57) {
|
||||
return label.substr(0, 57).trim() + '...';
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private getFilterTextForValue(value): string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
private getSuggestionKind(type: any): CompletionItemKind {
|
||||
if (Array.isArray(type)) {
|
||||
let array = <any[]>type;
|
||||
type = array.length > 0 ? array[0] : null;
|
||||
}
|
||||
if (!type) {
|
||||
return CompletionItemKind.Value;
|
||||
}
|
||||
switch (type) {
|
||||
case 'string': return CompletionItemKind.Value;
|
||||
case 'object': return CompletionItemKind.Module;
|
||||
case 'property': return CompletionItemKind.Property;
|
||||
default: return CompletionItemKind.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private getCurrentWord(document: TextDocument, offset: number) {
|
||||
var i = offset - 1;
|
||||
var text = document.getText();
|
||||
while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
|
||||
i--;
|
||||
}
|
||||
return text.substring(i + 1, offset);
|
||||
}
|
||||
|
||||
private findItemAtOffset(node: Parser.ASTNode, document: TextDocument, offset: number) {
|
||||
let scanner = Json.createScanner(document.getText(), true);
|
||||
let children = node.getChildNodes();
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
let child = children[i];
|
||||
if (offset > child.end) {
|
||||
scanner.setPosition(child.end);
|
||||
let token = scanner.scan();
|
||||
if (token === Json.SyntaxKind.CommaToken && offset >= scanner.getTokenOffset() + scanner.getTokenLength()) {
|
||||
return i + 1;
|
||||
}
|
||||
return i;
|
||||
} else if (offset >= child.start) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private isInComment(document: TextDocument, start: number, offset: number) {
|
||||
let scanner = Json.createScanner(document.getText(), false);
|
||||
scanner.setPosition(start);
|
||||
let token = scanner.scan();
|
||||
while (token !== Json.SyntaxKind.EOF && (scanner.getTokenOffset() + scanner.getTokenLength() < offset)) {
|
||||
token = scanner.scan();
|
||||
}
|
||||
return (token === Json.SyntaxKind.LineCommentTrivia || token === Json.SyntaxKind.BlockCommentTrivia) && scanner.getTokenOffset() <= offset;
|
||||
}
|
||||
}
|
||||
130
src/yaml-languageservice/services/yamlHover.ts
Normal file
130
src/yaml-languageservice/services/yamlHover.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
import Parser = require('../parser/jsonParser');
|
||||
import SchemaService = require('./jsonSchemaService');
|
||||
import {JSONWorkerContribution} from '../jsonContributions';
|
||||
import {PromiseConstructor, Thenable} from 'vscode-json-languageservice';
|
||||
|
||||
import {Hover, TextDocument, Position, Range, MarkedString} from 'vscode-languageserver-types';
|
||||
import { matchOffsetToDocument } from '../utils/arrUtils';
|
||||
|
||||
export class YAMLHover {
|
||||
|
||||
private schemaService: SchemaService.IJSONSchemaService;
|
||||
private contributions: JSONWorkerContribution[];
|
||||
private promise: PromiseConstructor;
|
||||
|
||||
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor: PromiseConstructor) {
|
||||
this.schemaService = schemaService;
|
||||
this.contributions = contributions;
|
||||
this.promise = promiseConstructor || Promise;
|
||||
}
|
||||
|
||||
public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<Hover> {
|
||||
|
||||
let offset = document.offsetAt(position);
|
||||
let currentDoc = matchOffsetToDocument(offset, doc);
|
||||
if(currentDoc === null){
|
||||
return null;
|
||||
}
|
||||
let node = currentDoc.getNodeFromOffset(offset);
|
||||
if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) {
|
||||
return this.promise.resolve(void 0);
|
||||
}
|
||||
let hoverRangeNode = node;
|
||||
|
||||
// use the property description when hovering over an object key
|
||||
if (node.type === 'string') {
|
||||
let stringNode = <Parser.StringASTNode>node;
|
||||
if (stringNode.isKey) {
|
||||
let propertyNode = <Parser.PropertyASTNode>node.parent;
|
||||
node = propertyNode.value;
|
||||
if (!node) {
|
||||
return this.promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hoverRange = Range.create(document.positionAt(hoverRangeNode.start), document.positionAt(hoverRangeNode.end));
|
||||
|
||||
var createHover = (contents: MarkedString[]) => {
|
||||
let result: Hover = {
|
||||
contents: contents,
|
||||
range: hoverRange
|
||||
};
|
||||
return result;
|
||||
};
|
||||
|
||||
let location = node.getPath();
|
||||
for (let i = this.contributions.length - 1; i >= 0; i--) {
|
||||
let contribution = this.contributions[i];
|
||||
let promise = contribution.getInfoContribution(document.uri, location);
|
||||
if (promise) {
|
||||
return promise.then(htmlContent => createHover(htmlContent));
|
||||
}
|
||||
}
|
||||
|
||||
return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
|
||||
if (schema) {
|
||||
|
||||
let matchingSchemas = currentDoc.getMatchingSchemas(schema.schema, node.start);
|
||||
|
||||
let title: string = null;
|
||||
let markdownDescription: string = null;
|
||||
let markdownEnumValueDescription = null, enumValue = null;
|
||||
matchingSchemas.every((s) => {
|
||||
if (s.node === node && !s.inverted && s.schema) {
|
||||
title = title || s.schema.title;
|
||||
markdownDescription = markdownDescription || s.schema["markdownDescription"] || toMarkdown(s.schema.description);
|
||||
if (s.schema.enum) {
|
||||
let idx = s.schema.enum.indexOf(node.getValue());
|
||||
if (s.schema["markdownEnumDescriptions"]) {
|
||||
markdownEnumValueDescription = s.schema["markdownEnumDescriptions"][idx];
|
||||
} else if (s.schema.enumDescriptions) {
|
||||
markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]);
|
||||
}
|
||||
if (markdownEnumValueDescription) {
|
||||
enumValue = s.schema.enum[idx];
|
||||
if (typeof enumValue !== 'string') {
|
||||
enumValue = JSON.stringify(enumValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
let result = '';
|
||||
if (title) {
|
||||
result = toMarkdown(title);
|
||||
}
|
||||
if (markdownDescription) {
|
||||
if (result.length > 0) {
|
||||
result += "\n\n";
|
||||
}
|
||||
result += markdownDescription;
|
||||
}
|
||||
if (markdownEnumValueDescription) {
|
||||
if (result.length > 0) {
|
||||
result += "\n\n";
|
||||
}
|
||||
result += `\`${toMarkdown(enumValue)}\`: ${markdownEnumValueDescription}`;
|
||||
}
|
||||
return createHover([result]);
|
||||
}
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toMarkdown(plain: string) {
|
||||
if (plain) {
|
||||
let res = plain.replace(/([^\n\r])(\r?\n)([^\n\r])/gm, '$1\n\n$3'); // single new lines to \n\n (Markdown paragraph)
|
||||
return res.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
75
src/yaml-languageservice/services/yamlValidation.ts
Normal file
75
src/yaml-languageservice/services/yamlValidation.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JSONSchemaService } from './jsonSchemaService';
|
||||
import { JSONDocument, ObjectASTNode, IProblem, ProblemSeverity } from '../parser/jsonParser';
|
||||
import { TextDocument, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types';
|
||||
import { PromiseConstructor, Thenable } from '../yamlLanguageService';
|
||||
import { LanguageSettings } from '../../vscode-yaml-languageservice/yamlLanguageService';
|
||||
|
||||
export class YAMLValidation {
|
||||
|
||||
private jsonSchemaService: JSONSchemaService;
|
||||
private promise: PromiseConstructor;
|
||||
private comments: boolean;
|
||||
private validationEnabled: boolean;
|
||||
|
||||
public constructor(jsonSchemaService, promiseConstructor) {
|
||||
this.jsonSchemaService = jsonSchemaService;
|
||||
this.promise = promiseConstructor;
|
||||
this.validationEnabled = true;
|
||||
}
|
||||
|
||||
public configure(shouldValidate: LanguageSettings) {
|
||||
if (shouldValidate) {
|
||||
this.validationEnabled = shouldValidate.validate;
|
||||
}
|
||||
}
|
||||
|
||||
public doValidation(textDocument, yamlDocument) {
|
||||
|
||||
if (!this.validationEnabled) {
|
||||
return this.promise.resolve([]);
|
||||
}
|
||||
|
||||
return this.jsonSchemaService.getSchemaForResource(textDocument.uri).then(function (schema) {
|
||||
if (schema) {
|
||||
|
||||
for (let currentYAMLDoc in yamlDocument.documents) {
|
||||
let currentDoc = yamlDocument.documents[currentYAMLDoc];
|
||||
let diagnostics = currentDoc.getValidationProblems(schema.schema);
|
||||
for (let diag in diagnostics) {
|
||||
let curDiagnostic = diagnostics[diag];
|
||||
currentDoc.errors.push({ location: { start: curDiagnostic.location.start, end: curDiagnostic.location.end }, message: curDiagnostic.message })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
var diagnostics = [];
|
||||
var added = {};
|
||||
for (let currentYAMLDoc in yamlDocument.documents) {
|
||||
let currentDoc = yamlDocument.documents[currentYAMLDoc];
|
||||
currentDoc.errors.concat(currentDoc.warnings).forEach(function (error, idx) {
|
||||
// remove duplicated messages
|
||||
var signature = error.location.start + ' ' + error.location.end + ' ' + error.message;
|
||||
if (!added[signature]) {
|
||||
added[signature] = true;
|
||||
var range = {
|
||||
start: textDocument.positionAt(error.location.start),
|
||||
end: textDocument.positionAt(error.location.end)
|
||||
};
|
||||
diagnostics.push({
|
||||
severity: idx >= currentDoc.errors.length ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
|
||||
range: range,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
});
|
||||
}
|
||||
}
|
||||
72
src/yaml-languageservice/utils/arrUtils.ts
Normal file
72
src/yaml-languageservice/utils/arrUtils.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { YAMLDocument } from "../../vscode-yaml-languageservice/yamlLanguageService";
|
||||
import { SingleYAMLDocument } from "../parser/yamlParser";
|
||||
|
||||
export function removeDuplicates(arr, prop) {
|
||||
var new_arr = [];
|
||||
var lookup = {};
|
||||
|
||||
for (var i in arr) {
|
||||
lookup[arr[i][prop]] = arr[i];
|
||||
}
|
||||
|
||||
for (i in lookup) {
|
||||
new_arr.push(lookup[i]);
|
||||
}
|
||||
|
||||
return new_arr;
|
||||
}
|
||||
|
||||
export function getLineOffsets(textDocString: String): number[] {
|
||||
|
||||
let lineOffsets: number[] = [];
|
||||
let text = textDocString;
|
||||
let isLineStart = true;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (isLineStart) {
|
||||
lineOffsets.push(i);
|
||||
isLineStart = false;
|
||||
}
|
||||
let ch = text.charAt(i);
|
||||
isLineStart = (ch === '\r' || ch === '\n');
|
||||
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (isLineStart && text.length > 0) {
|
||||
lineOffsets.push(text.length);
|
||||
}
|
||||
|
||||
return lineOffsets;
|
||||
}
|
||||
|
||||
export function removeDuplicatesObj(objArray) {
|
||||
|
||||
let nonDuplicateSet = new Set();
|
||||
let nonDuplicateArr = [];
|
||||
for (let obj in objArray) {
|
||||
|
||||
let currObj = objArray[obj];
|
||||
let stringifiedObj = JSON.stringify(currObj);
|
||||
if (!nonDuplicateSet.has(stringifiedObj)) {
|
||||
nonDuplicateArr.push(currObj);
|
||||
nonDuplicateSet.add(stringifiedObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nonDuplicateArr;
|
||||
|
||||
}
|
||||
|
||||
export function matchOffsetToDocument(offset: number, jsonDocuments): SingleYAMLDocument {
|
||||
|
||||
for (let jsonDoc in jsonDocuments.documents) {
|
||||
let currJsonDoc = jsonDocuments.documents[jsonDoc];
|
||||
if (currJsonDoc.root && currJsonDoc.root.end >= offset && currJsonDoc.root.start <= offset) {
|
||||
return currJsonDoc;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
62
src/yaml-languageservice/utils/documentPositionCalculator.ts
Normal file
62
src/yaml-languageservice/utils/documentPositionCalculator.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"use strict"
|
||||
|
||||
export function insertionPointReturnValue(pt: number) {
|
||||
return ((-pt) - 1)
|
||||
}
|
||||
|
||||
export function binarySearch(array: number[], sought: number) {
|
||||
|
||||
let lower = 0
|
||||
let upper = array.length - 1
|
||||
|
||||
while (lower <= upper) {
|
||||
let idx = Math.floor((lower + upper) / 2)
|
||||
const value = array[idx]
|
||||
|
||||
if (value === sought) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if (lower === upper) {
|
||||
const insertionPoint = (value < sought) ? idx + 1 : idx
|
||||
return insertionPointReturnValue(insertionPoint)
|
||||
}
|
||||
|
||||
if (sought > value) {
|
||||
lower = idx + 1;
|
||||
} else if (sought < value) {
|
||||
upper = idx - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getLineStartPositions(text: string) {
|
||||
const lineStartPositions = [0];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
const c = text[i];
|
||||
|
||||
if (c === '\r') {
|
||||
// Check for Windows encoding, otherwise we are old Mac
|
||||
if (i + 1 < text.length && text[i + 1] == '\n') {
|
||||
i++;
|
||||
}
|
||||
|
||||
lineStartPositions.push(i + 1);
|
||||
} else if (c === '\n'){
|
||||
lineStartPositions.push(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return lineStartPositions;
|
||||
}
|
||||
|
||||
export function getPosition(pos: number, lineStartPositions: number[]){
|
||||
let line = binarySearch(lineStartPositions, pos)
|
||||
|
||||
if (line < 0){
|
||||
const insertionPoint = -1 * line - 1;
|
||||
line = insertionPoint - 1;
|
||||
}
|
||||
|
||||
return {line, column: pos - lineStartPositions[line]}
|
||||
}
|
||||
32
src/yaml-languageservice/utils/errorHandler.ts
Normal file
32
src/yaml-languageservice/utils/errorHandler.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Red Hat, Inc. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { DiagnosticSeverity } from "vscode-languageserver/lib/main";
|
||||
|
||||
export class ErrorHandler {
|
||||
private errorResultsList;
|
||||
private textDocument;
|
||||
|
||||
constructor(textDocument){
|
||||
this.errorResultsList = [];
|
||||
this.textDocument = textDocument;
|
||||
}
|
||||
|
||||
public addErrorResult(errorNode, errorMessage, errorType){
|
||||
this.errorResultsList.push({
|
||||
severity: errorType,
|
||||
range: {
|
||||
start: this.textDocument.positionAt(errorNode.startPosition),
|
||||
end: this.textDocument.positionAt(errorNode.endPosition)
|
||||
},
|
||||
message: errorMessage
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public getErrorResultsList(){
|
||||
return this.errorResultsList;
|
||||
}
|
||||
|
||||
}
|
||||
58
src/yaml-languageservice/utils/objects.ts
Normal file
58
src/yaml-languageservice/utils/objects.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function equals(one: any, other: any): boolean {
|
||||
if (one === other) {
|
||||
return true;
|
||||
}
|
||||
if (one === null || one === undefined || other === null || other === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (typeof one !== typeof other) {
|
||||
return false;
|
||||
}
|
||||
if (typeof one !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if ((Array.isArray(one)) !== (Array.isArray(other))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var i: number,
|
||||
key: string;
|
||||
|
||||
if (Array.isArray(one)) {
|
||||
if (one.length !== other.length) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < one.length; i++) {
|
||||
if (!equals(one[i], other[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var oneKeys: string[] = [];
|
||||
|
||||
for (key in one) {
|
||||
oneKeys.push(key);
|
||||
}
|
||||
oneKeys.sort();
|
||||
var otherKeys: string[] = [];
|
||||
for (key in other) {
|
||||
otherKeys.push(key);
|
||||
}
|
||||
otherKeys.sort();
|
||||
if (!equals(oneKeys, otherKeys)) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < oneKeys.length; i++) {
|
||||
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
37
src/yaml-languageservice/utils/strings.ts
Normal file
37
src/yaml-languageservice/utils/strings.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export function startsWith(haystack: string, needle: string): boolean {
|
||||
if (haystack.length < needle.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < needle.length; i++) {
|
||||
if (haystack[i] !== needle[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if haystack ends with needle.
|
||||
*/
|
||||
export function endsWith(haystack: string, needle: string): boolean {
|
||||
let diff = haystack.length - needle.length;
|
||||
if (diff > 0) {
|
||||
return haystack.lastIndexOf(needle) === diff;
|
||||
} else if (diff === 0) {
|
||||
return haystack === needle;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function convertSimple2RegExpPattern(pattern: string): string {
|
||||
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
|
||||
}
|
||||
351
src/yaml-languageservice/utils/uri.ts
Normal file
351
src/yaml-languageservice/utils/uri.ts
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
function _encode(ch: string): string {
|
||||
return '%' + ch.charCodeAt(0).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
|
||||
function encodeURIComponent2(str: string): string {
|
||||
return encodeURIComponent(str).replace(/[!'()*]/g, _encode);
|
||||
}
|
||||
|
||||
function encodeNoop(str: string): string {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986.
|
||||
* This class is a simple parser which creates the basic component paths
|
||||
* (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation
|
||||
* and encoding.
|
||||
*
|
||||
* foo://example.com:8042/over/there?name=ferret#nose
|
||||
* \_/ \______________/\_________/ \_________/ \__/
|
||||
* | | | | |
|
||||
* scheme authority path query fragment
|
||||
* | _____________________|__
|
||||
* / \ / \
|
||||
* urn:example:animal:ferret:nose
|
||||
*
|
||||
*
|
||||
*/
|
||||
export default class URI {
|
||||
|
||||
private static _empty = '';
|
||||
private static _slash = '/';
|
||||
private static _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;
|
||||
private static _driveLetterPath = /^\/[a-zA-z]:/;
|
||||
private static _upperCaseDrive = /^(\/)?([A-Z]:)/;
|
||||
|
||||
private _scheme: string;
|
||||
private _authority: string;
|
||||
private _path: string;
|
||||
private _query: string;
|
||||
private _fragment: string;
|
||||
private _formatted: string;
|
||||
private _fsPath: string;
|
||||
|
||||
constructor() {
|
||||
this._scheme = URI._empty;
|
||||
this._authority = URI._empty;
|
||||
this._path = URI._empty;
|
||||
this._query = URI._empty;
|
||||
this._fragment = URI._empty;
|
||||
|
||||
this._formatted = null;
|
||||
this._fsPath = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part before the first colon.
|
||||
*/
|
||||
get scheme() {
|
||||
return this._scheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
* The part between the first double slashes and the next slash.
|
||||
*/
|
||||
get authority() {
|
||||
return this._authority;
|
||||
}
|
||||
|
||||
/**
|
||||
* path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
/**
|
||||
* query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get query() {
|
||||
return this._query;
|
||||
}
|
||||
|
||||
/**
|
||||
* fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'.
|
||||
*/
|
||||
get fragment() {
|
||||
return this._fragment;
|
||||
}
|
||||
|
||||
// ---- filesystem path -----------------------
|
||||
|
||||
/**
|
||||
* Returns a string representing the corresponding file system path of this URI.
|
||||
* Will handle UNC paths and normalize windows drive letters to lower-case. Also
|
||||
* uses the platform specific path separator. Will *not* validate the path for
|
||||
* invalid characters and semantics. Will *not* look at the scheme of this URI.
|
||||
*/
|
||||
get fsPath() {
|
||||
if (!this._fsPath) {
|
||||
var value: string;
|
||||
if (this._authority && this.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${this._authority}${this._path}`;
|
||||
} else if (URI._driveLetterPath.test(this._path)) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = this._path[1].toLowerCase() + this._path.substr(2);
|
||||
} else {
|
||||
// other path
|
||||
value = this._path;
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
this._fsPath = value;
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
|
||||
public with(scheme: string, authority: string, path: string, query: string, fragment: string): URI {
|
||||
var ret = new URI();
|
||||
ret._scheme = scheme || this.scheme;
|
||||
ret._authority = authority || this.authority;
|
||||
ret._path = path || this.path;
|
||||
ret._query = query || this.query;
|
||||
ret._fragment = fragment || this.fragment;
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public withScheme(value: string): URI {
|
||||
return this.with(value, undefined, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withAuthority(value: string): URI {
|
||||
return this.with(undefined, value, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public withPath(value: string): URI {
|
||||
return this.with(undefined, undefined, value, undefined, undefined);
|
||||
}
|
||||
|
||||
public withQuery(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, value, undefined);
|
||||
}
|
||||
|
||||
public withFragment(value: string): URI {
|
||||
return this.with(undefined, undefined, undefined, undefined, value);
|
||||
}
|
||||
|
||||
// ---- parse & validate ------------------------
|
||||
|
||||
public static parse(value: string): URI {
|
||||
const ret = new URI();
|
||||
const data = URI._parseComponents(value);
|
||||
ret._scheme = data.scheme;
|
||||
ret._authority = decodeURIComponent(data.authority);
|
||||
ret._path = decodeURIComponent(data.path);
|
||||
ret._query = decodeURIComponent(data.query);
|
||||
ret._fragment = decodeURIComponent(data.fragment);
|
||||
URI._validate(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static file(path: string): URI {
|
||||
|
||||
const ret = new URI();
|
||||
ret._scheme = 'file';
|
||||
|
||||
// normalize to fwd-slashes
|
||||
path = path.replace(/\\/g, URI._slash);
|
||||
|
||||
// check for authority as used in UNC shares
|
||||
// or use the path as given
|
||||
if (path[0] === URI._slash && path[0] === path[1]) {
|
||||
let idx = path.indexOf(URI._slash, 2);
|
||||
if (idx === -1) {
|
||||
ret._authority = path.substring(2);
|
||||
} else {
|
||||
ret._authority = path.substring(2, idx);
|
||||
ret._path = path.substring(idx);
|
||||
}
|
||||
} else {
|
||||
ret._path = path;
|
||||
}
|
||||
|
||||
// Ensure that path starts with a slash
|
||||
// or that it is at least a slash
|
||||
if (ret._path[0] !== URI._slash) {
|
||||
ret._path = URI._slash + ret._path;
|
||||
}
|
||||
|
||||
URI._validate(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static _parseComponents(value: string): UriComponents {
|
||||
|
||||
const ret: UriComponents = {
|
||||
scheme: URI._empty,
|
||||
authority: URI._empty,
|
||||
path: URI._empty,
|
||||
query: URI._empty,
|
||||
fragment: URI._empty,
|
||||
};
|
||||
|
||||
const match = URI._regexp.exec(value);
|
||||
if (match) {
|
||||
ret.scheme = match[2] || ret.scheme;
|
||||
ret.authority = match[4] || ret.authority;
|
||||
ret.path = match[5] || ret.path;
|
||||
ret.query = match[7] || ret.query;
|
||||
ret.fragment = match[9] || ret.fragment;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static create(scheme?: string, authority?: string, path?: string, query?: string, fragment?: string): URI {
|
||||
return new URI().with(scheme, authority, path, query, fragment);
|
||||
}
|
||||
|
||||
private static _validate(ret: URI): void {
|
||||
|
||||
// validation
|
||||
// path, http://tools.ietf.org/html/rfc3986#section-3.3
|
||||
// If a URI contains an authority component, then the path component
|
||||
// must either be empty or begin with a slash ("/") character. If a URI
|
||||
// does not contain an authority component, then the path cannot begin
|
||||
// with two slash characters ("//").
|
||||
if (ret.authority && ret.path && ret.path[0] !== '/') {
|
||||
throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character');
|
||||
}
|
||||
if (!ret.authority && ret.path.indexOf('//') === 0) {
|
||||
throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* @param skipEncoding Do not encode the result, default is `false`
|
||||
*/
|
||||
public toString(skipEncoding: boolean = false): string {
|
||||
if (!skipEncoding) {
|
||||
if (!this._formatted) {
|
||||
this._formatted = URI._asFormatted(this, false);
|
||||
}
|
||||
return this._formatted;
|
||||
} else {
|
||||
// we don't cache that
|
||||
return URI._asFormatted(this, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static _asFormatted(uri: URI, skipEncoding: boolean): string {
|
||||
|
||||
const encoder = !skipEncoding
|
||||
? encodeURIComponent2
|
||||
: encodeNoop;
|
||||
|
||||
const parts: string[] = [];
|
||||
|
||||
let { scheme, authority, path, query, fragment } = uri;
|
||||
if (scheme) {
|
||||
parts.push(scheme, ':');
|
||||
}
|
||||
if (authority || scheme === 'file') {
|
||||
parts.push('//');
|
||||
}
|
||||
if (authority) {
|
||||
authority = authority.toLowerCase();
|
||||
let idx = authority.indexOf(':');
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(authority));
|
||||
} else {
|
||||
parts.push(encoder(authority.substr(0, idx)), authority.substr(idx));
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
// lower-case windown drive letters in /C:/fff
|
||||
const m = URI._upperCaseDrive.exec(path);
|
||||
if (m) {
|
||||
path = m[1] + m[2].toLowerCase() + path.substr(m[1].length + m[2].length);
|
||||
}
|
||||
|
||||
// encode every segement but not slashes
|
||||
// make sure that # and ? are always encoded
|
||||
// when occurring in paths - otherwise the result
|
||||
// cannot be parsed back again
|
||||
let lastIdx = 0;
|
||||
while (true) {
|
||||
let idx = path.indexOf(URI._slash, lastIdx);
|
||||
if (idx === -1) {
|
||||
parts.push(encoder(path.substring(lastIdx)).replace(/[#?]/, _encode));
|
||||
break;
|
||||
}
|
||||
parts.push(encoder(path.substring(lastIdx, idx)).replace(/[#?]/, _encode), URI._slash);
|
||||
lastIdx = idx + 1;
|
||||
};
|
||||
}
|
||||
if (query) {
|
||||
parts.push('?', encoder(query));
|
||||
}
|
||||
if (fragment) {
|
||||
parts.push('#', encoder(fragment));
|
||||
}
|
||||
|
||||
return parts.join(URI._empty);
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
return <UriState>{
|
||||
scheme: this.scheme,
|
||||
authority: this.authority,
|
||||
path: this.path,
|
||||
fsPath: this.fsPath,
|
||||
query: this.query,
|
||||
fragment: this.fragment,
|
||||
external: this.toString(),
|
||||
$mid: 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface UriComponents {
|
||||
scheme: string;
|
||||
authority: string;
|
||||
path: string;
|
||||
query: string;
|
||||
fragment: string;
|
||||
}
|
||||
|
||||
interface UriState extends UriComponents {
|
||||
$mid: number;
|
||||
fsPath: string;
|
||||
external: string;
|
||||
}
|
||||
125
src/yaml-languageservice/yamlLanguageService.ts
Normal file
125
src/yaml-languageservice/yamlLanguageService.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { JSONSchemaService } from './services/jsonSchemaService'
|
||||
import { TextDocument, Position, CompletionList } from 'vscode-languageserver-types';
|
||||
import { JSONSchema } from './jsonSchema';
|
||||
import { parse as parseYAML } from "./parser/yamlParser";
|
||||
import { YAMLDocumentSymbols } from './services/documentSymbols';
|
||||
import { YAMLCompletion } from "./services/yamlCompletion";
|
||||
import { YAMLHover } from "./services/yamlHover";
|
||||
import { YAMLValidation } from "./services/yamlValidation";
|
||||
import { LanguageSettings, YAMLDocument, Diagnostic } from '../vscode-yaml-languageservice/yamlLanguageService';
|
||||
|
||||
export interface LanguageSettings {
|
||||
validate?: boolean; //Setting for whether we want to validate the schema
|
||||
schemas?: any[]; //List of schemas
|
||||
}
|
||||
|
||||
export interface PromiseConstructor {
|
||||
/**
|
||||
* Creates a new Promise.
|
||||
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
|
||||
* a resolve callback used resolve the spromise with a value or the result of another promise,
|
||||
* and a reject callback used to reject the promise with a provided reason or error.
|
||||
*/
|
||||
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Thenable<T>;
|
||||
|
||||
/**
|
||||
* Creates a Promise that is resolved with an array of results when all of the provided Promises
|
||||
* resolve, or rejected when any Promise is rejected.
|
||||
* @param values An array of Promises.
|
||||
* @returns A new Promise.
|
||||
*/
|
||||
all<T>(values: Array<T | Thenable<T>>): Thenable<T[]>;
|
||||
/**
|
||||
* Creates a new rejected promise for the provided reason.
|
||||
* @param reason The reason the promise was rejected.
|
||||
* @returns A new rejected Promise.
|
||||
*/
|
||||
reject<T>(reason: any): Thenable<T>;
|
||||
|
||||
/**
|
||||
* Creates a new resolved promise for the provided value.
|
||||
* @param value A promise.
|
||||
* @returns A promise whose internal state matches the provided promise.
|
||||
*/
|
||||
resolve<T>(value: T | Thenable<T>): Thenable<T>;
|
||||
|
||||
}
|
||||
|
||||
export interface Thenable<R> {
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
* @param onrejected The callback to execute when the Promise is rejected.
|
||||
* @returns A Promise for the completion of which ever callback is executed.
|
||||
*/
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||
}
|
||||
|
||||
export interface WorkspaceContextService {
|
||||
resolveRelativePath(relativePath: string, resource: string): string;
|
||||
}
|
||||
/**
|
||||
* The schema request service is used to fetch schemas. The result should the schema file comment, or,
|
||||
* in case of an error, a displayable error string
|
||||
*/
|
||||
export interface SchemaRequestService {
|
||||
(uri: string): Thenable<string>;
|
||||
}
|
||||
|
||||
export interface SchemaConfiguration {
|
||||
/**
|
||||
* The URI of the schema, which is also the identifier of the schema.
|
||||
*/
|
||||
uri: string;
|
||||
/**
|
||||
* A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json'
|
||||
*/
|
||||
fileMatch?: string[];
|
||||
/**
|
||||
* The schema for the given URI.
|
||||
* If no schema is provided, the schema will be fetched with the schema request service (if available).
|
||||
*/
|
||||
schema?: JSONSchema;
|
||||
}
|
||||
|
||||
export interface LanguageService {
|
||||
configure(settings): void;
|
||||
parseYAMLDocument(document: TextDocument): YAMLDocument,
|
||||
doComplete(document: TextDocument, position: Position, doc): Thenable<CompletionList>;
|
||||
doValidation(document: TextDocument, yamlDocument): Thenable<Diagnostic[]>;
|
||||
doHover(document: TextDocument, position: Position, doc);
|
||||
findDocumentSymbols(document: TextDocument, doc);
|
||||
doResolve(completionItem);
|
||||
resetSchema(uri: string): boolean;
|
||||
}
|
||||
|
||||
export function getLanguageService(schemaRequestService, workspaceContext, contributions, customSchemaProvider, promiseConstructor?): LanguageService {
|
||||
let promise = promiseConstructor || Promise;
|
||||
|
||||
let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext, customSchemaProvider);
|
||||
|
||||
let completer = new YAMLCompletion(schemaService, contributions, promise);
|
||||
let hover = new YAMLHover(schemaService, contributions, promise);
|
||||
let yamlDocumentSymbols = new YAMLDocumentSymbols();
|
||||
let yamlValidation = new YAMLValidation(schemaService, promise);
|
||||
|
||||
return {
|
||||
configure: (settings) => {
|
||||
schemaService.clearExternalSchemas();
|
||||
if (settings.schemas) {
|
||||
settings.schemas.forEach(settings => {
|
||||
schemaService.registerExternalSchema(settings.uri, settings.fileMatch, settings.schema);
|
||||
});
|
||||
}
|
||||
yamlValidation.configure(settings);
|
||||
},
|
||||
parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText()),
|
||||
doComplete: completer.doComplete.bind(completer),
|
||||
doResolve: completer.doResolve.bind(completer),
|
||||
doValidation: yamlValidation.doValidation.bind(yamlValidation),
|
||||
doHover: hover.doHover.bind(hover),
|
||||
findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols),
|
||||
resetSchema: (uri: string) => schemaService.onResourceChange(uri)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue