Import external libs

This commit is contained in:
Kevin Decker 2018-02-25 19:53:44 -06:00
parent ff043657bd
commit 39dcb0e50b
56 changed files with 8385 additions and 3 deletions

View file

@ -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'
}]
})
}

View file

@ -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
View file

@ -0,0 +1 @@
export const EOL = '\n';

View 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]}
}

View 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)));
}

View file

@ -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);
}

View 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)]
}

View 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"
]
}
}

View 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
};
}

View 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);
}

View 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));
}

View 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;
}
}

View 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'

File diff suppressed because it is too large Load diff

View 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;
}
}

View 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;
}

View 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
});
}
}

View 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
]
});

View 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;

View 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
]
})

View 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
]
});

View 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
]
});

View 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.');
}
}
}

View 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
});

View 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'
});

View 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'
});

View 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']
}
});

View 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
});

View 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
});

View 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
});

View 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 : {}; }
});

View 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
});

View 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'
});

View 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
});

View 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
});

View 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 : []; }
});

View 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
});

View 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 : ''; }
});

View 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
});

View 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
}
}

View 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;
}

View 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;
}

File diff suppressed because it is too large Load diff

View 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)));
}

View 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;
}
}
}

View 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;
}

View 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;
}
}

View 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;
}

View 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;
});
}
}

View 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;
}

View 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]}
}

View 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;
}
}

View 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;
}

View 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, '.*');
}

View 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;
}

View 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)
}
}