UNPKG

jsx

Version:

a faster, safer, easier JavaScript

383 lines (312 loc) 10.9 kB
/* * Copyright (c) 2012 DeNA Co., Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ import "./analysis.jsx"; import "./classdef.jsx"; import "./type.jsx"; import "./expression.jsx"; import "./util.jsx"; import "./parser.jsx"; /* example: { "word" : "stringify", "partialWord" : "ringify", "doc" : "serialize a value or object as a JSON string", "type" : "function (value : variant) : string", "returnType" : "string", "args" : [ { "name" : "value", "type" : "variant" } ], "definedClass" : "JSON", "definedFilename" : "lib/built-in/jsx", "definedLineNumber" : 903 } */ class CompletionRequest { var _lineNumber : number; var _columnOffest : number; var _candidates : CompletionCandidates[]; function constructor (lineNumber : number, columnOffset : number) { this._lineNumber = lineNumber; this._columnOffest = columnOffset; this._candidates = new CompletionCandidates[]; } function getLineNumber () : number { return this._lineNumber; } function getColumnOffset () : number { return this._columnOffest; } function isInRange (lineNumber : number, columnOffset : number, length : number) : number { if (lineNumber != this._lineNumber) return -1; if (columnOffset <= this._columnOffest && this._columnOffest <= columnOffset + length) { return this._columnOffest - columnOffset; } return -1; } function pushCandidates (candidates : CompletionCandidates) : void { this._candidates.push(candidates); } function getCandidates () : Map.<variant>[] { var seen = new Map.<boolean>; // for unique var results = new Map.<variant>[]; // fetch the list this._candidates.forEach(function (candidates : CompletionCandidates) : void { var rawCandidates = new Map.<variant>[]; candidates.getCandidates(rawCandidates); var prefix = candidates.getPrefix(); rawCandidates.forEach(function (s) { var word = s["word"] as string; if (prefix == "" && word.substring(0, 2) == "__" && word != "__noconvert__" && word != "undefined") { // skip hidden keywords } else if (word.substring(0, prefix.length) == prefix) { var left = word.substring(prefix.length); if (left.length == 0) { return; } var identity = JSON.stringify([left, s["args"]] : variant[]); if (! seen.hasOwnProperty(identity)) { seen[identity] = true; if (word != left) { s["partialWord"] = left; } // "kind" is useful for debugging --completion itself, // but unlikely to be used by editors delete s["kind"]; results.push(s); } } }); }); return results; } } abstract class CompletionCandidates { var _prefix : Nullable.<string>; function constructor () { this._prefix = null; } abstract function getCandidates (candidates : Map.<variant>[]) : void; function getPrefix () : string { return this._prefix; } function setPrefix (prefix : string) : CompletionCandidates { this._prefix = prefix; return this; } static function makeClassCandidate (classDef : ClassDefinition) : Map.<variant> { var data = new Map.<variant>; data["word"] = classDef.className(); data["definedFilename"] = classDef.getToken().getFilename(); data["definedLineNumber"] = classDef.getToken().getLineNumber(); if ((classDef.flags() & ClassDefinition.IS_INTERFACE) != 0) { data["kind"] = "interface"; } else if ((classDef.flags() & ClassDefinition.IS_MIXIN) != 0) { data["kind"] = "mixin"; } else { data["kind"] = "class"; } var docComment = classDef.getDocComment(); if (docComment) { data["doc"] = docComment.getDescription(); } return data; } static function _addClasses (candidates : Map.<variant>[], parser : Parser, autoCompleteMatchCb : function(:ClassDefinition):boolean) : void { parser.getClassDefs().forEach(function (classDef) { if (classDef instanceof InstantiatedClassDefinition) { // skip } else { if (autoCompleteMatchCb == null || autoCompleteMatchCb(classDef)) { candidates.push(CompletionCandidates.makeClassCandidate(classDef)); } } }); parser.getTemplateClassDefs().forEach(function (classDef) { if (autoCompleteMatchCb == null || autoCompleteMatchCb(classDef)) { candidates.push(CompletionCandidates.makeClassCandidate(classDef)); } }); } static function _addImportedClasses (candidates : Map.<variant>[], imprt : Import, autoCompleteMatchCb : function(:ClassDefinition):boolean) : void { var classNames = imprt.getClassNames(); if (classNames != null) { classNames.forEach(function (className) { var data = new Map.<variant>; data["word"] = className; data["kind"] = "class"; // FIXME can we refer to the classdefs of the classnames here? candidates.push(data); }); } else { imprt.getSources().forEach(function (parser) { CompletionCandidates._addClasses(candidates, parser, autoCompleteMatchCb); }); } } } class KeywordCompletionCandidate extends CompletionCandidates { var _expected : string; function constructor (expected : string) { super(); this._expected = expected; } override function getCandidates (candidates : Map.<variant>[]) : void { var data = new Map.<variant>; data["word"] = this._expected; data["kind"] = "keyword"; candidates.push(data); } } class CompletionCandidatesOfTopLevel extends CompletionCandidates { var _parser : Parser; var _autoCompleteMatchCb : function(:ClassDefinition):boolean; function constructor (parser : Parser, autoCompleteMatchCb : function(:ClassDefinition):boolean) { super(); this._parser = parser; this._autoCompleteMatchCb = autoCompleteMatchCb; } override function getCandidates (candidates : Map.<variant>[]) : void { CompletionCandidates._addClasses(candidates, this._parser, this._autoCompleteMatchCb); for (var i = 0; i < this._parser._imports.length; ++i) { var imprt = this._parser._imports[i]; var alias = imprt.getAlias(); if (alias != null) { var data = new Map.<variant>; data["word"] = alias; data["kind"] = "alias"; candidates.push(data); } else { CompletionCandidates._addImportedClasses(candidates, imprt, this._autoCompleteMatchCb); } } } } class _CompletionCandidatesWithLocal extends CompletionCandidatesOfTopLevel { var _locals : LocalVariable[]; function constructor (parser : Parser) { super(parser, null); this._locals = new LocalVariable[]; parser._forEachScope(function (funcName, locals, args) { if (funcName != null) this._locals = this._locals.concat([ funcName ]); this._locals = this._locals.concat(locals); for (var i in args) { this._locals.push(args[i]); } return true; }); } override function getCandidates (candidates : Map.<variant>[]) : void { this._locals.forEach(function (local) { var data = new Map.<variant>; data["word"] = local.getName().getValue(); data["kind"] = 'variable'; data["definedFilename"] = local.getName().getFilename(); data["definedLineNumber"] = local.getName().getLineNumber(); var type = local.getType(); // type may be null when type deduction fails if (type != null) { data["type"] = type.toString(); } candidates.push(data); }); super.getCandidates(candidates); } } class _CompletionCandidatesOfNamespace extends CompletionCandidates { var _import : Import; var _autoCompleteMatchCb : function(:ClassDefinition):boolean; function constructor (imprt : Import, autoCompleteMatchCb : function(:ClassDefinition):boolean) { super(); this._import = imprt; this._autoCompleteMatchCb = autoCompleteMatchCb; } override function getCandidates (candidates : Map.<variant>[]) : void { CompletionCandidates._addImportedClasses(candidates, this._import, this._autoCompleteMatchCb); } } class _CompletionCandidatesOfProperty extends CompletionCandidates { var _expr : Expression; function constructor (expr : Expression) { super(); this._expr = expr; } override function getCandidates (candidates : Map.<variant>[]) : void { var type = this._expr.getType(); if (type == null) return; type = type.resolveIfNullable(); if (type.equals(Type.voidType) || type.equals(Type.nullType) || type.equals(Type.variantType)) return; // type with classdef var classDef = type.getClassDef(); if (classDef == null) return; var isStatic = this._expr instanceof ClassExpression; classDef.forEachClassToBase(function (c) { c.forEachMember(function (member) { if (((member.flags() & ClassDefinition.IS_STATIC) != 0) == isStatic) { if (! isStatic && member.name() == "constructor") { return true; } candidates.push(_CompletionCandidatesOfProperty._makeMemberCandidate(member)); } return true; }); return true; }); } static function _makeMemberCandidate (member : MemberDefinition) : Map.<variant> { var kind = (member.flags() & ClassDefinition.IS_STATIC ? "static member" : "member"); kind += (member instanceof MemberFunctionDefinition ? " function" : " variable"); var data = new Map.<variant>; data["word"] = member.name(); data["type"] = member.getType().toString(); data["kind"] = kind; data["definedClass"] = member.getClassDef().className(); data["definedFilename"] = member.getToken().getFilename(); data["definedLineNumber"] = member.getToken().getLineNumber(); var docComment = member.getDocComment(); if (docComment) { data["doc"] = docComment.getDescription(); } if (member instanceof MemberFunctionDefinition) { var mf = member as MemberFunctionDefinition; data["returnType"] = mf.getReturnType().toString(); data["args"] = mf.getArguments().map.<Map.<string>>(function (arg) { var pair = new Map.<string>; pair["name"] = arg.getName().getValue(); pair["type"] = arg.getType().toString(); return pair; }); } return data; } } // vim: set noexpandtab: