Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aulx

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aulx - npm Package Compare versions

Comparing version 13.1.18 to 13.6.1

.swp

117

entrance/completers.js

@@ -9,5 +9,3 @@ // Map from language file extensions to functions that can autocomplete the

// * ch: the column of the caret, starting with zero.
// - options: Object containing optional parameters:
// * line: String of the current line (which the editor may provide
// more efficiently than the default way.
// - options: Object containing optional parameters.
//

@@ -17,5 +15,3 @@ // Return an object with the following fields:

// - completions: A list of the associated completion to a candidate.
var completer = {
js: jsCompleter
};
var completer = {};

@@ -33,31 +29,94 @@ exports = completer;

function Map() {
// Cut off the inheritance tree.
this.map = Object.create(null);
// Firefox landed Maps without forEach, hence the odd check for that.
var Map = this.Map;
if (!(Map && Map.prototype.forEach)) {
var Map = function Map() {};
Map.prototype = Object.create(null, {
get: {
enumerable: false,
value: function(key) {
return this[key];
}
},
has: {
enumerable: false,
value: function(key) {
return this[key] !== undefined;
}
},
set: {
enumerable: false,
value: function(key, value) {
this[key] = value;
}
},
delete: {
enumerable: false,
value: function(key) {
if (this.has(key)) {
delete this[key];
return true;
} else {
return false;
}
}
},
forEach: {
enumerable: false,
value: function(callbackfn, thisArg) {
callbackfn = callbackfn.bind(thisArg);
for (var i in this) {
callbackfn(this[i], i, this);
}
}
},
});
}
Map.prototype = {
get: function(key) {
return this.map[key];
// Completion-related data structures.
//
// The only way to distinguish two candidates is through how they are displayed.
// That's how the user can tell the difference, too.
function Candidate(display, postfix, score) {
this.display = display; // What the user sees.
this.postfix = postfix; // What is added when selected.
this.score = score|0; // Its score.
}
function Completion() {
this.candidateFromDisplay = new Map();
this.candidates = [];
}
Completion.prototype = {
insert: function(candidate) {
this.candidateFromDisplay.set(candidate.display, candidate);
this.candidates.push(candidate);
},
has: function(key) {
return this.map[key] !== undefined;
},
set: function(key, value) {
this.map[key] = value;
},
delete: function(key) {
if (this.has(key)) {
delete this.map[key];
return true;
} else {
return false;
meld: function(completion) {
for (var i = 0; i < completion.candidates.length; i++) {
var candidate = completion.candidates[i];
if (!this.candidateFromDisplay.has(candidate.display)) {
// We do not already have this candidate.
this.insert(candidate);
}
}
},
forEach: function(callbackfn, thisArg) {
callbackfn = callbackfn.bind(thisArg);
for (var i in this.map) {
callbackfn(this.map[i], i, this);
}
sort: function() {
this.candidates.sort(function(a, b) {
// A huge score comes first.
return b.score - a.score;
});
}
};
// Shared function: inRange.
// Detect whether an index is within a range.
function inRange(index, range) {
return index > range[0] && index <= range[1];
}
// test.js: A module for unit tests.
// Copyright © 2011-2013 Jan Keromnes, Thaddee Tyl. All rights reserved.
// Copyright © 2011-2013 Thaddee Tyl, Jan Keromnes. All rights reserved.
// Code covered by the LGPL license.

@@ -4,0 +4,0 @@

@@ -0,1 +1,3 @@

// FIXME: make a constructor to allow a stateful autocompletion engine.
//

@@ -19,17 +21,38 @@ // Get a list of completions we can have, based on the state of the editor.

// - options: Object containing optional parameters:
// * line: String of the current line (which the editor may provide
// more efficiently than the default way.
// * contextFrom: Part of the source necessary to get the context.
// May be a string of the current line (which the editor may provide
// more efficiently than the default way).
// Use this if you know that reduceContext() is too slow for you.
// * global: global object. Can be used to perform level 1 (see above).
// * parse: a JS parser that is compatible with
// https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
// * parserContinuation: boolean. If true, the parser has a callback argument
// which is called with the AST.
// * tokenize: a JS tokenizer that is compatible with Esprima.
// * fireStaticAnalysis: A Boolean to run the (possibly expensive) static
// analysis. Recommendation: run it at every newline.
// analysis. Recommendation: run it at every change of line.
// // FIXME: put this functionality in a separate method.
// * globalIdentifier: A String to identify the symbol representing the
// JS global object, such as 'window' (the default), for static analysis
// purposes.
//
// Return an object with the following fields:
// - candidates: A list of the matches to a possible completion.
// - completions: A list of the associated completion to a candidate.
// Return a sorted Completion (see entrance/completers.js).
// - candidateFromDisplay: Map from display string to candidate.
// - candidates: A list of candidates:
// * display: a string of what the user sees.
// * postfix: a string of what is added when the user chooses this.
// * score: a number to grade the candidate.
//
function jsCompleter(source, caret, options) {
options = options || {};
var candidates = [];
var completions = [];
var completion = new Completion();
// Caching the result of a static analysis for perf purposes.
// Only do this (possibly expensive) operation when required.
if (staticCandidates == null || options.fireStaticAnalysis) {
updateStaticCache(source, caret,
{ parse: options.parse,
parserContinuation: options.parserContinuation });
}
// We use a primitive sorting algorithm.

@@ -41,37 +64,17 @@ // The candidates are simply concatenated, level after level.

var context = getContext(source, caret);
var context = getContext(options.contextFrom || source, caret,
options.tokenizer);
if (!context) {
// We couldn't get the context, we won't be able to complete.
return completion;
}
// Static analysis (Level 2).
// Only do this (possibly expensive) operation once every new line.
if (staticCandidates == null || options.fireStaticAnalysis) {
staticCandidates = getStaticScope(source, caret)
|| staticCandidates; // If it fails, use the previous version.
if (staticCandidates != null) {
// They have a non-negative score.
var staticCompletion = staticAnalysis(context,
{globalIdentifier: options.globalIdentifier});
if (!!staticCompletion) { completion.meld(staticCompletion); }
}
var allStaticCandidates = staticCandidates;
// Right now, we can only complete variables.
if ((context.completion === Completion.identifier ||
context.completion === Completion.property) &&
context.data.length === 1 && allStaticCandidates != null) {
var varName = context.data[0];
var staticCandidates = [];
allStaticCandidates.forEach(function (value, key) {
var candidate = key;
var weight = value;
// The candidate must match and have something to add!
if (candidate.indexOf(varName) == 0
&& candidate.length > varName.length) {
staticCandidates.push(candidate);
}
});
staticCandidates.sort(function(a, b) {
// Sort them according to nearest scope.
return allStaticCandidates.get(b) - allStaticCandidates.get(a);
});
candidates = candidates.concat(staticCandidates);
completions = completions.concat(staticCandidates
.map(function(candidate) {
return candidate.slice(varName.length);
}));
}

@@ -81,13 +84,5 @@ // Sandbox-based candidates (Level 1).

if (options.global !== undefined) {
// They have a score of -1.
var sandboxCompletion = identifierLookup(options.global, context);
if (sandboxCompletion) {
sandboxCompletion.candidates = sandboxCompletion.candidates
.filter(function(candidate) {
// We are removing candidates from level 2.
if (allStaticCandidates == null) return true;
return !allStaticCandidates.has(candidate);
});
candidates = candidates.concat(sandboxCompletion.candidates);
completions = completions.concat(sandboxCompletion.completions);
}
if (!!sandboxCompletion) { completion.meld(sandboxCompletion); }
}

@@ -97,48 +92,48 @@

var keywords = [
"break", "case", "catch", "class", "continue", "debugger",
"default", "delete", "do", "else", "export", "false", "finally", "for",
"function", "get", "if", "import", "in", "instanceof", "let", "new",
"null", "of", "return", "set", "super", "switch", "this", "true", "throw",
"try", "typeof", "undefined", "var", "void", "while", "with",
];
// This autocompletion is only meaningful with
if (context.completion === Completion.identifier &&
// This autocompletion is only meaningful with identifiers.
if (context.completing === Completing.identifier &&
context.data.length === 1) {
for (var i = 0; i < keywords.length; i++) {
var keyword = keywords[i];
var keywordCompletion = new Completion();
for (var keyword in JSKeywords) {
// The keyword must match and have something to add!
if (keyword.indexOf(context.data) == 0
&& keyword.length > context.data.length) {
candidates.push(keyword);
completions.push(keyword.slice(context.data.length));
if (keyword.indexOf(context.data[0]) == 0
&& keyword.length > context.data[0].length) {
keywordCompletion.insert(new Candidate(
keyword,
keyword.slice(context.data[0].length),
JSKeywords[keyword]));
// The score depends on the frequency of the keyword.
// See keyword.js
}
}
completion.meld(keywordCompletion);
}
return {
candidates: candidates,
completions: completions,
};
completion.sort();
return completion;
}
exports.js = jsCompleter;
// Generic helpers.
//
var esprima = esprima || exports;
// Autocompletion types.
var Completion = { // Examples.
var Completing = { // Examples.
identifier: 0, // foo.ba|
property: 1, // foo.|
string: 2, // "foo".|
regex: 3 // /foo/.|
};
jsCompleter.Completion = Completion;
jsCompleter.Completing = Completing;
// Fetch data from the position of the caret in a source.
// The data is an object containing the following:
// - completion: a number from the Completion enumeration.
// - completing: a number from the Completing enumeration.
// - data: information about the context. Ideally, a list of strings.

@@ -148,3 +143,3 @@ //

// (with the caret at the end of baz, even if after whitespace)
// will return `{completion:0, data:["foo", "bar", "baz"]}`.
// will return `{completing:0, data:["foo", "bar", "baz"]}`.
//

@@ -156,10 +151,8 @@ // If we cannot get an identifier, returns `null`.

// - caret: an object {line: 0-indexed line, ch: 0-indexed column}.
function getContext(source, caret) {
var tokens = esprima.tokenize(source);
if (tokens[tokens.length - 1].type !== esprima.Token.EOF) {
// If the last token is not an EOF, we didn't tokenize it correctly.
// This special case is handled in case we couldn't tokenize, but the last
// token that *could be tokenized* was an identifier.
return null;
}
function getContext(source, caret, tokenize) {
tokenize = tokenize || esprima.tokenize;
var reduction = reduceContext('' + source, caret);
if (reduction === null) { return null; }
caret = reduction[1];
var tokens = tokenize(reduction[0], {loc:true});

@@ -172,15 +165,17 @@ // At this point, we know we were able to tokenize it.

var tokIndex = (tokens.length / 2) | 0; // Truncating to an integer.
var tokIndexPrevValue = tokIndex;
var lastCall = false;
var token;
while (tokIndex !== lowIndex) {
while (lowIndex <= highIndex) {
token = tokens[tokIndex];
// Note: esprima line numbers start with 1, while caret starts with 0.
if (token.lineNumber - 1 < caret.line) {
lowIndex = tokIndex;
} else if (token.lineNumber - 1 > caret.line) {
if (!token) { return null; }
// Note: The caret is on the first line (as a result of reduceContext).
// Also, Esprima lines start with 1.
if (token.loc.start.line > 1) {
highIndex = tokIndex;
} else if (token.lineNumber - 1 === caret.line) {
} else {
// Now, we need the correct column.
var range = [
token.range[0] - token.lineStart,
token.range[1] - token.lineStart,
token.loc.start.column,
token.loc.end.column
];

@@ -193,34 +188,64 @@ if (inRange(caret.ch, range)) {

} else if (range[1] < caret.ch) {
lowIndex = tokIndex;
lowIndex = tokIndex + 1;
}
}
tokIndex = ((highIndex + lowIndex) / 2) | 0;
tokIndex = (highIndex + lowIndex) >>> 1;
if (lastCall) { break; }
if (tokIndex === tokIndexPrevValue) {
tokIndex++;
lastCall = true;
} else { tokIndexPrevValue = tokIndex; }
}
return contextFromToken(tokens, tokIndex, caret);
}
};
jsCompleter.getContext = getContext;
function inRange(index, range) {
return index > range[0] && index <= range[1];
}
// Either
//
// {
// completing: Completing.<type of completion>,
// data: <Array of string>
// }
//
// or undefined.
//
// Parameters:
// - tokens: list of tokens.
// - tokIndex: index of the token where the caret is.
// - caret: {line:0, ch:0}, position of the caret.
function contextFromToken(tokens, tokIndex, caret) {
var token = tokens[tokIndex];
if (token.type === esprima.Token.Punctuator &&
token.value === '.') {
// Property completion.
return {
completion: Completion.property,
data: suckIdentifier(tokens, tokIndex, caret)
};
} else if (token.type === esprima.Token.Identifier) {
var prevToken = tokens[tokIndex - 1];
if (!token) { return; }
if (token.type === "Punctuator" && token.value === '.') {
if (prevToken) {
if (prevToken.type === "Identifier" ||
(prevToken.type === "Keyword" && prevToken.value === "this")) {
// Property completion.
return {
completing: Completing.property,
data: suckIdentifier(tokens, tokIndex, caret)
};
} else if (prevToken.type === "String") {
// String completion.
return {
completing: Completing.string,
data: [] // No need for data.
};
} else if (prevToken.type === "RegularExpression") {
// Regex completion.
return {
completing: Completing.regex,
data: [] // No need for data.
};
}
}
} else if (token.type === "Identifier") {
// Identifier completion.
return {
completion: Completion.identifier,
completing: Completing.identifier,
data: suckIdentifier(tokens, tokIndex, caret)
};
} else {
return null;
}
}
};

@@ -236,8 +261,4 @@ // suckIdentifier aggregates the whole identifier into a list of strings, taking

var token = tokens[tokIndex];
if (token.type === esprima.Token.EOF) {
tokIndex--;
token = tokens[tokIndex];
}
if (token.type !== esprima.Token.Identifier &&
token.type !== esprima.Token.Punctuator) {
if (token.type !== "Identifier" &&
token.type !== "Punctuator") {
// Nothing to suck. Nothing to complete.

@@ -249,7 +270,8 @@ return null;

var identifier = [];
while (token.type === esprima.Token.Identifier ||
(token.type === esprima.Token.Punctuator &&
token.value === '.')) {
if (token.type === esprima.Token.Identifier) {
var endCh = token.range[1] - token.lineStart;
while (token.type === "Identifier" ||
(token.type === "Punctuator" && token.value === '.') ||
(token.type === "Keyword" && token.value === "this")) {
if (token.type === "Identifier" ||
token.type === "Keyword") {
var endCh = token.loc.end.column;
var tokValue;

@@ -271,2 +293,282 @@ if (caret.ch < endCh) {

return identifier;
};
// Reduce the amount of source code to contextualize,
// and the re-positionned caret in this smaller source code.
//
// For instance, `foo\nfoo.bar.baz|`
// will return `['foo.bar.baz', {line:0, ch:11}]`.
//
// If we cannot get an identifier, returns `null`.
//
// Parameters:
// - source: a string of JS code.
// - caret: an object {line: 0-indexed line, ch: 0-indexed column}.
function reduceContext(source, caret) {
var line = 0;
var column = 0;
var fakeCaret = {line: caret.line, ch: caret.ch};
// Find the position of the previous line terminator.
var iLT = 0;
var newSpot;
var changedLine = false;
var haveSkipped = false;
var i = 0;
var ch;
var nextch;
while ((line < caret.line
|| (line === caret.line && column < caret.ch))
&& i < source.length) {
ch = source.charCodeAt(i);
// Count the lines.
if (isLineTerminator(ch)) {
line++;
column = 0;
i++;
iLT = i;
continue;
} else {
column++;
}
if (ch === 34 || ch === 39) {
// Single / double quote: starts a string.
newSpot = skipStringLiteral(source, i, iLT - 1, line, column);
haveSkipped = true;
changedLine = line < newSpot.line;
i = newSpot.index;
line = newSpot.line;
column = newSpot.column;
} else if (ch === 47) {
// Slash.
nextch = source.charCodeAt(i + 1);
prevch = source.charCodeAt(i - 1);
if (nextch === 42 && prevch !== 92) {
// Star: we have a multiline comment.
// Not a backslash before: it isn't in a regex.
newSpot = skipMultilineComment(source, i, line, column);
haveSkipped = true;
changedLine = line < newSpot.line;
i = newSpot.index;
line = newSpot.line;
column = newSpot.column;
} else if (nextch === 47) {
// Two consecutive slashes: we have a single-line comment.
i++;
while (!isLineTerminator(ch) && i < source.length) {
ch = source.charCodeAt(i);
i++;
column++;
}
// `i` is on a line terminator.
i -= 2;
}
}
if (haveSkipped && isLineTerminator(source.charCodeAt(i))) {
haveSkipped = false;
continue;
}
if (changedLine) {
// Have we gone too far?
if (line > caret.line || line === caret.line && column > caret.ch + 1) {
return null;
} else if (line === caret.line) {
iLT = i;
// We need to reset the fake caret's position.
column = 0;
}
changedLine = false;
} else {
i++;
}
}
fakeCaret.line = 0;
fakeCaret.ch = column;
// We can limit tokenization between beginning of line
// to position of the caret.
return [source.slice(iLT, iLT + column + 1), fakeCaret];
}
// Useful functions stolen from Esprima.
// Invisible characters.
// 7.2 White Space
function isWhiteSpace(ch) {
return (ch === 32) || // space
(ch === 9) || // tab
(ch === 0xB) ||
(ch === 0xC) ||
(ch === 0xA0) ||
(ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
}
// 7.3 Line Terminators
function isLineTerminator(ch) {
return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
}
// Strings.
// 7.8.4 String Literals
// This Esprima algorithm was heavily modified for my purposes.
//
// Parameters:
// - source: code
// - index: position of the opening quote.
// - indexAtStartOfLine: position of the first character of the current line,
// minus one.
// - lineNumber: starting from 0.
// - column: number.
//
// It returns the following object:
// - index: of the character after the end.
// - line: line number at the end of the string.
// - column: column number of the character after the end.
function skipStringLiteral(source, index, indexAtStartOfLine,
lineNumber, column) {
var quote, ch, code, restore;
var length = source.length;
quote = source[index];
++index;
while (index < length) {
ch = source[index++];
if (ch === quote) {
break;
} else if (ch === '\\') {
ch = source[index++];
if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
switch (ch) {
case 'n': break;
case 'r': break;
case 't': break;
case 'u':
case 'x':
restore = index;
index = scanHexEscape(source, index, ch);
if (index < 0) {
index = restore;
}
break;
case 'b': break;
case 'f': break;
case 'v': break;
default:
if (isOctalDigit(ch)) {
code = '01234567'.indexOf(ch);
if (index < length && isOctalDigit(source[index])) {
code = code * 8 + '01234567'.indexOf(source[index++]);
// 3 digits are only allowed when string starts
// with 0, 1, 2, 3
if ('0123'.indexOf(ch) >= 0 &&
index < length &&
isOctalDigit(source[index])) {
code = code * 8 + '01234567'.indexOf(source[index++]);
}
}
}
break;
}
} else {
++lineNumber;
if (ch === '\r' && source[index] === '\n') {
++index;
}
indexAtStartOfLine = index;
}
} else if (isLineTerminator(ch.charCodeAt(0))) {
++lineNumber;
indexAtStartOfLine = index;
break;
}
}
return {
index: index,
line: lineNumber,
column: index - indexAtStartOfLine
};
}
function scanHexEscape(source, index, prefix) {
var i, len, ch, code = 0;
len = (prefix === 'u') ? 4 : 2;
for (i = 0; i < len; ++i) {
if (index < source.length && isHexDigit(source[index])) {
ch = source[index++];
code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
} else {
return -1;
}
}
return index;
}
function isOctalDigit(ch) {
return '01234567'.indexOf(ch) >= 0;
}
function isHexDigit(ch) {
return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
}
// The following function is not from Esprima.
// The index must be positioned in the source on a slash
// that starts a multiline comment.
//
// It returns the following object:
// - index: of the character after the end.
// - line: line number at the end of the comment.
// - column: column number of the character after the end.
function skipMultilineComment(source, index, line, targetLine, column) {
var ch = 47;
while (index < source.length) {
ch = source[index].charCodeAt(0);
if (ch == 42) {
// Current character is a star.
if (index === source.length - 1) {
break;
}
if (source[index + 1].charCodeAt(0) === 47) {
// Next character is a slash.
index += 2;
column += 2;
break;
}
}
index++;
if (isLineTerminator(ch)) {
line++;
column = 0;
} else {
column++;
}
}
return {
index: index,
line: line,
column: column
};
}
// Sandbox-based analysis.
//
// Return an object with the following fields:
// - candidates: A list of the matches to a possible completion.
// - completions: A list of the associated completion to a candidate.
// Return a sorted Completion (see entrance/completers.js).
// - candidateFromDisplay: Map from display string to candidate.
// - candidates: A list of candidates:
// * display: a string of what the user sees.
// * postfix: a string of what is added when the user chooses this.
// * score: a number to grade the candidate.
//

@@ -13,30 +16,19 @@ // Parameters:

// See ./main.js.
function identifierLookup(global, context) {
function identifierLookup(global, context, options) {
var matchProp = '';
var completion = new Completion();
var value = global;
if (context.completion === Completion.identifier) {
// foo.ba|
for (var i = 0; i < context.data.length - 1; i++) {
var descriptor = getPropertyDescriptor(value, context.data[i]);
if (descriptor.get) {
// This is a getter / setter.
// We might trigger a side-effect by going deeper.
// We must stop before the world blows up in a Michael Bay manner.
value = null;
break;
} else {
// We need to go deeper. One property deeper.
value = value[context.data[i]];
}
}
if (value != null) {
var symbols;
var store = staticCandidates;
if (context.completing === Completing.identifier || // foo.ba|
context.completing === Completing.property) { // foo.|
symbols = context.data;
if (context.completing === Completing.identifier) {
symbols = context.data.slice(0, -1);
matchProp = context.data[context.data.length - 1];
}
} else if (context.completion === Completion.property) {
// foo.|
for (var i = 0; i < context.data.length; i++) {
var descriptor = getPropertyDescriptor(value, context.data[i]);
if (descriptor.get) {
for (var i = 0; i < symbols.length; i++) {
var descriptor = getPropertyDescriptor(value, symbols[i]);
if (descriptor && descriptor.get) {
// This is a getter / setter.

@@ -49,19 +41,57 @@ // We might trigger a side-effect by going deeper.

// We need to go deeper. One property deeper.
value = value[context.data[i]];
value = value[symbols[i]];
if (value == null) { break; }
}
}
dynAnalysisFromType(completion, symbols, global, matchProp);
} else if (context.completing === Completing.string) {
// "foo".|
value = global.String.prototype;
} else if (context.completing === Completing.regex) {
// /foo/.|
value = global.RegExp.prototype;
}
var result = {candidates: [], completions: []};
if (value != null) {
var matchedProps = getMatchedProps(value, { matchProp: matchProp });
result.candidates = Object.keys(matchedProps);
result.completions = result.candidates.map(function (prop) {
return prop.slice(matchProp.length);
completionFromValue(completion, value, matchProp);
}
return completion;
}
// completion: a Completion object,
// symbols: a list of strings of properties.
// global: a JS global object.
// matchProp: the start of the property name to complete.
function dynAnalysisFromType(completion, symbols, global, matchProp) {
var store = staticCandidates;
for (var i = 0; i < symbols.length; i++) {
store = store.properties.get(symbols[i]);
}
// Get the type of this property.
if (!!store) {
store.type.forEach(function(sourceIndices, funcName) {
// The element is an instance of that class (source index = 0).
if (sourceIndices.indexOf(0) >= 0 && global[funcName]) {
completionFromValue(completion, global[funcName].prototype, matchProp);
}
});
return result;
}
}
} else {
// We cannot give any completion.
return result; // empty result.
// completion: a Completion object,
// value: a JS object
// matchProp: a string of the start of the property to complete.
function completionFromValue(completion, value, matchProp) {
var matchedProps = getMatchedProps(value, { matchProp: matchProp });
for (var prop in matchedProps) {
// It needs to be a valid property: this is dot completion.
try {
var tokens = esprima.tokenize(prop);
if (tokens.length === 1 && tokens[0].type === "Identifier") {
completion.insert(
new Candidate(prop, prop.slice(matchProp.length), -1));
}
} catch (e) {} // Definitely not a valid property.
}

@@ -71,3 +101,2 @@ }

// Get all accessible properties on this JS value, as an Object.

@@ -74,0 +103,0 @@ // Filter those properties by name.

@@ -0,7 +1,89 @@

// Return a Completion instance, or undefined.
// Parameters:
// - context: result of the getContext function.
// - options:
// * globalIdentifier: the string of a global parameter.
// For instance, `global`, or `window` (the default).
function staticAnalysis(context, options) {
options = options || {};
options.globalIdentifier = options.globalIdentifier || 'window';
var staticCompletion = new Completion();
var completingIdentifier = (context.completing === Completing.identifier);
var completingProperty = (context.completing === Completing.property);
var varName; // Each will modify this to the start of the variable name.
var eachProperty = function eachProperty(store, display) {
if (display.indexOf(varName) == 0
&& display.length > varName.length) {
// The candidate must match and have something to add!
try {
var tokens = esprima.tokenize(display);
if (tokens.length === 1 && tokens[0].type === "Identifier") {
staticCompletion.insert(new Candidate(display,
display.slice(varName.length), store.weight));
}
} catch (e) {} // Definitely not a valid property.
}
};
if (completingIdentifier && context.data.length === 1) {
varName = context.data[0];
// They have a positive score.
staticCandidates.properties.forEach(eachProperty);
if (options.globalIdentifier &&
staticCandidates.properties[options.globalIdentifier]) {
// Add properties like `window.|`.
staticCandidates.properties[options.globalIdentifier].properties
.forEach(eachProperty);
}
} else if (completingIdentifier || completingProperty) {
var store = staticCandidates;
for (var i = 0; i < context.data.length - 1; i++) {
store = store.properties.get(context.data[i]);
if (!store) { return; }
}
varName = context.data[i];
if (completingProperty) {
store = store.properties.get(varName);
if (!store) { return; }
varName = ''; // This will cause the indexOf check to succeed.
}
store.properties.forEach(eachProperty);
// Seek data from its type.
if (!!store.type) {
store.type.forEach(function(sourceIndices, funcName) {
funcStore = staticCandidates.properties.get(funcName);
if (!funcStore) { return; }
for (var i = 0; i < store.type[funcName].length; i++) {
var sourceIndex = store.type[funcName][i];
// Each sourceIndex corresponds to a source,
// and the `sources` property is that source.
if (funcStore.sources) {
funcStore.sources[sourceIndex].forEach(eachProperty);
if (sourceIndex === 0) {
// This was a constructor.
var protostore = funcStore.properties.get('prototype');
if (!protostore) { return; }
protostore.properties.forEach(eachProperty);
}
}
}
});
}
}
return staticCompletion;
}
// Static analysis helper functions.
//
// Cache in use for static analysis.
var staticCandidates; // We keep the previous candidates around.
//

@@ -12,5 +94,3 @@ // Get all the variables in a JS script at a certain position.

//
// Returns a map from all variable names to a number reflecting how deeply
// nested in the scope the variable was. A bigger number reflects a more
// deeply nested variable.
// Returns a TypeStore object. See below.
// We return null if we could not parse the code.

@@ -23,18 +103,37 @@ //

// - source: The JS script to parse.
// - caret: {line:0, ch:0} The line and column in the script from which we want the scope.
// - store:
// (Optional) The object we return. Use to avoid allocation.
// - depth:
// (Optional, defaults to 0.) A starting point for indicating how deeply
// nested variables are.
// - caret: {line:0, ch:0} The line and column in the scrip
// from which we want the scope.
// - options:
// * store: The object we return. Use to avoid allocation.
// It is a TypeStore.
// * parse: A JS parser that conforms to
// https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
// * parserContinuation: A boolean. If true, the parser has a callback
// argument that sends the AST.
//
function getStaticScope(source, caret, store, depth) {
store = store || new Map();
depth = depth || 0;
function updateStaticCache(source, caret, options) {
options = options || {};
options.store = options.store || new TypeStore();
options.parse = options.parse || esprima.parse;
var tree;
try {
tree = esprima.parse(source, {loc:true});
if (!!options.parserContinuation) {
options.parse(source, {loc:true}, function(tree) {
staticCandidates = getStaticScope(tree, caret, options)
|| staticCandidates; // If it fails, use the previous version.
});
} else {
var tree = options.parse(source, {loc:true});
staticCandidates = getStaticScope(tree, caret, options)
|| staticCandidates; // If it fails, use the previous version.
}
} catch (e) { return null; }
}
jsCompleter.updateStaticCache = updateStaticCache;
function getStaticScope(tree, caret, options) {
var subnode, symbols;
var store = options.store;
var node = tree.body;

@@ -48,3 +147,3 @@ var stack = [];

for (; index < node.length; index++) {
var subnode = node[index];
subnode = node[index];
while (["ReturnStatement", "VariableDeclarator", "ExpressionStatement",

@@ -57,3 +156,19 @@ "AssignmentExpression", "Property"].indexOf(subnode.type) >= 0) {

// Variable names go one level too deep.
store.set(subnode.id.name, stack.length - 1);
if (subnode.init && subnode.init.type === "NewExpression") {
store.addProperty(subnode.id.name, // property name
{ name: subnode.init.callee.name, // atomic type
index: 0 }, // created from `new C()`
stack.length - 1); // weight
store.addProperty(subnode.init.callee.name,
{ name: 'Function', index: 0 });
// FIXME: add built-in types detection.
} else if (subnode.init && subnode.init.type === "Literal" ||
subnode.init && subnode.init.type === "ObjectExpression" ||
subnode.init && subnode.init.type === "ArrayExpression") {
typeFromLiteral(store, [subnode.id.name], subnode.init);
store.properties.get(subnode.id.name).weight = stack.length - 1;
} else {
// Simple object.
store.addProperty(subnode.id.name, null, stack.length - 1);
}
if (!!subnode.init) {

@@ -68,4 +183,19 @@ subnode = subnode.init;

if (subnode.type == "AssignmentExpression") {
if (subnode.left.type === "MemberExpression") {
symbols = typeFromMember(store, subnode.left);
}
if (subnode.right.type === "ObjectExpression") {
typeFromLiteral(store, symbols, subnode.right);
}
subnode = subnode.right; // f.g = function(){…};
}
if (subnode.type == "CallExpression") {
if (subnode.callee.name) { // f()
store.addProperty(subnode.callee.name,
{ name: 'Function', index: 0 },
stack.length);
} else if (!subnode.callee.body) { // f.g()
typeFromMember(store, subnode.callee);
}
}
if (subnode.type == "Property") {

@@ -83,3 +213,6 @@ subnode = subnode.value; // {f: function(){…}};

if (subnode.id) {
store.set(subnode.id.name, stack.length);
store.addProperty(subnode.id.name,
{ name: 'Function', index: 0 },
stack.length);
readThisProps(store, subnode);
}

@@ -130,5 +263,3 @@ if (caretInBlock(subnode, caret)) {

} else if (node.consequent) {
body = node.consequent.body; // If statements.
} else if (node.alternate) {
body = node.alternate.body; // If/else statements.
body = fakeIfNodeList(node); // If statements.
} else if (node.block) {

@@ -159,2 +290,20 @@ body = node.block.body; // Try statements.

//
// Construct a list of nodes to go through based on the sequence of ifs and else
// ifs and elses.
//
// Parameters:
// - node: an AST node of type IfStatement.
function fakeIfNodeList(node) {
var body = [node.consequent];
if (node.alternate) {
if (node.alternate.type === "IfStatement") {
body = body.concat(fakeIfNodeList(node.alternate));
} else if (node.alternate.type === "BlockStatement") {
body.push(node.alternate);
}
}
return body;
}
//
// Whether the caret is in the piece of code represented by the node.

@@ -193,5 +342,199 @@ //

for (var i = 0; i < node.length; i++) {
store.set(node[i].name, weight);
store.addProperty(node[i].name, null, weight);
}
}
//
// Type inference.
// A type is a list of sources.
//
// *Sources* can be either:
//
// - The result of a `new Constructor()` call.
// - The result of a function.
// - A parameter to a function.
//
// Each function stores information in the TypeStore about all possible sources
// it can give, as a list of sources (aka maps to typestores):
//
// [`this` properties, return properties, param1, param2, etc.]
//
// Each instance stores information about the list of sources it may come from.
// Inferred information about the properties of each instance comes from the
// aggregated properties of each source.
// The type is therefore a map of the following form.
//
// { "name of the original function": [list of indices of source] }
//
// We may represent atomic type outside a compound type as the following:
//
// { name: "name of the origin", index: source index }
//
// A type inference instance maps symbols to an object of the following form:
// - properties: a Map from property symbols to typeStores for its properties,
// - type: a structural type (ie, not atomic) (see above).
// - weight: integer, relevance of the symbol,
function TypeStore(type, weight) {
this.properties = new Map();
this.type = type || new Map();
this.weight = weight|0;
if (this.type.has("Function")) {
// The sources for properties on `this` and on the return object.
this.sources = [new Map(), new Map()];
}
}
TypeStore.prototype = {
// Add a property named `symbol` typed from the atomic type `atype`.
// `atype` and `weight` may not be present.
addProperty: function(symbol, atype, weight) {
if (!this.properties.has(symbol)) {
if (atype != null) {
var newType = new Map();
var typeSources = [atype.index];
newType.set(atype.name, typeSources);
}
this.properties.set(symbol, new TypeStore(newType, weight));
} else {
// The weight is proportional to the frequency.
var p = this.properties.get(symbol);
p.weight++; // FIXME: this increment is questionnable.
if (atype != null) {
p.addType(atype);
}
}
},
// Given an atomic type (name, index), is this one?
hasType: function(atype) {
if (!this.type.has(atype.name)) { return false; }
return this.type.get(atype.name).indexOf(atype.index) >= 0;
},
// We can add an atomic type (a combination of the name of the original
// function and the source index) to an existing compound type.
addType: function(atype) {
if (atype.name === "Function") {
// The sources for properties on `this` and on the return object.
this.sources = this.sources || [new Map(), new Map()];
}
if (this.type.has(atype.name)) {
// The original function name is already known.
var sourceIndices = this.type.get(atype.name);
if (sourceIndices.indexOf(atype.index) === -1) {
sourceIndices.push(atype.index);
}
} else {
// New original function name (common case).
var sourceIndices = [];
sourceIndices.push(atype.index);
this.type.set(atype.name, sourceIndices);
}
}
};
// Store is a TypeStore instance,
// node is a MemberExpression.
// funName is the name of the containing function.
// Having funName set prevents setting properties on `this`.
function typeFromMember(store, node, funName) {
var symbols, symbol, i;
symbols = [];
symbol = '';
while (node.object && // `foo()` doesn't have a `.object`.
node.object.type !== "Identifier" &&
node.object.type !== "ThisExpression") {
symbols.push(node.property.name);
node = node.object;
}
symbols.push(node.property.name);
if (node.object.type !== "ThisExpression") {
symbols.push(node.object.name); // At this point, node is an identifier.
} else {
// Add the `this` properties to the function's generic properties.
var func = store.properties.get(funName);
if (!!func) {
for (i = symbols.length - 1; i >= 0; i--) {
symbol = symbols[i];
func.sources[0].set(symbol, new TypeStore());
func = func.properties.get(symbol);
}
return symbols;
} else if (!!funName) {
// Even if we don't have a function, we must stop there
// if funName is defined.
return symbols;
}
// Treat `this` as a variable inside the function.
symbols.push("this");
}
// Now that we have the symbols, put them in the store.
symbols.reverse();
for (i = 0; i < symbols.length; i++) {
symbol = symbols[i];
store.addProperty(symbol);
store = store.properties.get(symbol);
}
return symbols;
}
// Store is a TypeStore instance,
// node is a Literal or an ObjectExpression.
function typeFromLiteral(store, symbols, node) {
var property, i, substore, nextSubstore;
substore = store;
// Find the substore insertion point.
for (i = 0; i < symbols.length; i++) {
nextSubstore = substore.properties.get(symbols[i]);
if (!nextSubstore) {
// It really should exist.
substore.addProperty(symbols[i]);
nextSubstore = substore.properties.get(symbols[i]);
}
substore = nextSubstore;
}
// Add the symbols.
var constructor = "Object";
if (node.type === "ObjectExpression") {
for (i = 0; i < node.properties.length; i++) {
property = node.properties[i];
var propname = property.key.name? property.key.name
: property.key.value;
substore.addProperty(propname);
if (property.value.type === "ObjectExpression") {
// We can recursively complete the object tree.
typeFromLiteral(store, symbols.concat(propname), property.value);
}
}
} else if (node.type === "ArrayExpression") {
constructor = 'Array';
} else if (node.value instanceof RegExp) {
constructor = 'RegExp';
} else if (typeof node.value === "number") {
constructor = 'Number';
} else if (typeof node.value === "string") {
constructor = 'String';
} else if (typeof node.value === "boolean") {
constructor = 'Boolean';
}
substore.addType({ name: constructor, index: 0 });
}
// Assumes that the function has an explicit name.
function readThisProps(store, node) {
var funcStore = store.properties.get(node.id.name);
var statements = node.body.body;
var i = 0;
for (; i < statements.length; i++) {
if (statements[i].expression &&
statements[i].expression.type === "AssignmentExpression" &&
statements[i].expression.left.type === "MemberExpression") {
typeFromMember(store, statements[i].expression.left, node.id.name);
}
}
}

@@ -9,9 +9,12 @@ // Testing files in this directory.

var source;
var caret;
// Testing main.js
// getContext(source, caret)
var source = 'var foo.bar;baz';
var caret = {line:0, ch:15};
source = 'var foo.bar;baz';
caret = {line:0, ch:15};
t.eq(jsCompleter.getContext(source, caret),
{ completion: jsCompleter.Completion.identifier,
{ completing: jsCompleter.Completing.identifier,
data: ['baz'] },

@@ -21,3 +24,3 @@ 'getContext cares for semi-colons.');

t.eq(jsCompleter.getContext(source, caret),
{ completion: jsCompleter.Completion.identifier,
{ completing: jsCompleter.Completing.identifier,
data: ['foo', 'bar'] },

@@ -27,10 +30,44 @@ "getContext takes all identifiers (doesn't stop with a dot).");

t.eq(jsCompleter.getContext(source, caret),
{ completion: jsCompleter.Completion.identifier,
{ completing: jsCompleter.Completing.identifier,
data: ['foo', 'ba'] },
"getContext cuts identifiers on the cursor.");
source = 'var foo.bar;\nbaz';
caret = {line:1, ch:3};
t.eq(jsCompleter.getContext(source, caret),
{ completing: jsCompleter.Completing.identifier,
data: ['baz'] },
"getContext deals with multiple lines.");
source = 'var foo/*.bar;\n bar*/ baz';
caret = {line:1, ch:10};
t.eq(jsCompleter.getContext(source, caret),
{ completing: jsCompleter.Completing.identifier,
data: ['baz'] },
"getContext deals with multiple line comments.");
source = 'var foo "bar\\\n bar" baz';
caret = {line:1, ch:9};
t.eq(jsCompleter.getContext(source, caret),
{ completing: jsCompleter.Completing.identifier,
data: ['baz'] },
"getContext deals with multiple line strings.");
source = 'var foo "bar\\\n bar';
caret = {line:1, ch:4};
t.eq(jsCompleter.getContext(source, caret),
undefined,
"getContext deals with untokenizable contexts.");
source = 'this.foo';
caret = {line:0, ch:8};
t.eq(jsCompleter.getContext(source, caret),
{ completing: jsCompleter.Completing.identifier,
data: ['this', 'foo'] },
"getContext deals with `this`.");
// Testing sandbox.js
var source = 'foo.ba';
var caret = {line:0, ch:source.length};
source = 'foo.ba';
caret = {line:0, ch:source.length};
// Create a global object with no inheritance.

@@ -40,15 +77,154 @@ var global = Object.create(null);

global.foo.bar = 0;
t.eq(jsCompleter(source, caret, {global:global}),
{candidates:['bar'], completions:['r']},
t.eq(jsCompleter(source, caret, {global:global}).candidates,
[{display:"bar", postfix:"r", score:-1}],
"The JS completer works with dynamic analysis.");
source = '"foo".';
caret = {line:0, ch:source.length};
// Fake String.prototype.
global.String = Object.create(null);
global.String.prototype = Object.create(null);
global.String.prototype.big = 1;
t.eq(jsCompleter(source, caret, {global:global}).candidates,
[{display:"big", postfix:"big", score:-1}],
"The JS completer does string completion.");
source = '/foo/.';
caret = {line:0, ch:source.length};
// Fake String.prototype.
global.RegExp = Object.create(null);
global.RegExp.prototype = Object.create(null);
global.RegExp.prototype.test = 'something';
t.eq(jsCompleter(source, caret, {global:global}).candidates,
[{display:"test", postfix:"test", score:-1}],
"The JS completer does RegExp completion.");
// Testing static.js
var source = 'var foobar; foo';
var caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}),
{candidates:['foobar'], completions:['bar']},
source = 'var foobar; foo';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"foobar", postfix:"bar", score:0}],
"The JS completer works with static analysis.");
source = 'foo.bar = 5; foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The JS completer has static object analysis in property assignments.");
source = 'foo.bar(); foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The JS completer has static object analysis in function calls.");
source = 'foo.bar = {baz:5}; foo.bar.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"baz", postfix:"az", score:0}],
"The JS completer has static object literal analysis " +
"in object assignments.");
source = 'var foo = {bar:5}; foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The JS completer has static object analysis in definition.");
source = 'var foo = {"bar":5}; foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The JS completer has static object analysis even with strings.");
source = 'this.bar = 5; this.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The JS completer has static object analysis even with strings.");
source = 'F.prototype.bar = 0; var foo = new F(); foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The static analysis goes through the prototype.");
source = 'var foo = {"b*": 0}; foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[],
"The static analysis doesn't complete non-identifiers.");
source = 'var foo = {b: {bar: 0}}; foo.b.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The static analysis sees types in objects.");
source = 'if (foo) {} else if (foo) {foo.bar = function () { foo.b }}';
caret = {line:0, ch:source.length - 3};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"The static analysis goes through else clauses.");
source = 'f.foo = {bar: 0}; f.foo.b';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates,
[{display:"bar", postfix:"ar", score:0}],
"Static analysis with assignment to property.");
source = 'var foo = 0; foo.b';
global = Object.create(null);
global.Number = Object.create(null);
global.Number.prototype = Object.create(null);
global.Number.prototype.bar = 0;
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret,
{fireStaticAnalysis:true, global:global}).candidates,
[{display:"bar", postfix:"ar", score:-1}],
"Static analysis maps literals to built-in types.");
source = 'var foo = []; foo.b';
global = Object.create(null);
global.Array = Object.create(null);
global.Array.prototype = Object.create(null);
global.Array.prototype.bar = 0;
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret,
{fireStaticAnalysis:true, global:global}).candidates,
[{display:"bar", postfix:"ar", score:-1}],
"Static analysis identifies array literals.");
source = 'window.quux = 0; qu';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret,
{fireStaticAnalysis:true, globalIdentifier:'window'}).candidates,
[{display:"quux", postfix:"ux", score:0}],
"Static analysis uses the globalIdentifier option.");
source = 'init(); ini';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret,
{fireStaticAnalysis:true}).candidates,
[{display:"init", postfix:"t", score:0}],
"Static analysis reads undefined functions.");
// Testing keyword completion
source = 'vo';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates[0].display,
"void",
"The JS completer knows keywords (or at least 'void').");
source = 'vo';
caret = {line:0, ch:source.length};
t.eq(jsCompleter(source, caret, {fireStaticAnalysis:true}).candidates[0].postfix,
"id",
"The JS completer completes keywords (or at least 'void').");
// The End.

@@ -55,0 +231,0 @@

@@ -25,2 +25,8 @@ // Contrary to popular belief, this file is meant to be a JS code concatenator.

// Web workers for static analysis.
bundle('demo/parser-worker.js', [
'node_modules/esprima/esprima.js',
'js/worker-parser.js',
]);
// Target environment: AMD / Node.js / plain old browsers.

@@ -30,7 +36,24 @@ //

'entrance/umd-begin.js',
'entrance/completers.js',
// JS completion files.
'entrance/compl-begin.js',
'js/main.js',
'js/static.js',
'js/sandbox.js',
'js/main.js',
'entrance/completers.js',
'js/keyword.js',
'entrance/compl-end.js',
// CSS completion files.
'entrance/compl-begin.js',
'css/main.js',
// Import tokenizer (with export shim).
'css/css-token-begin.js',
'css/tokenizer.js',
'css/css-token-end.js',
// Properties.
'css/properties.js',
'entrance/compl-end.js',
'entrance/umd-end.js',
]);
{
"name": "aulx",
"description": "Autocompletion for the Web. Includes JS with static and dynamic analysis.",
"author": "Thaddee Tyl <thaddee.tyl@gmail.com> (http://espadrine.github.com/)",

@@ -6,5 +8,4 @@ "contributors": [

],
"name": "aulx",
"description": "Autocompletion for the Web. Includes JS with static and dynamic analysis.",
"version": "13.01.18",
"main": "aulx.js",
"version": "13.06.01",
"homepage": "http://espadrine.github.com/aulx/",

@@ -15,20 +16,14 @@ "repository": {

},
"main": "lib/camp.js",
"scripts": {
"test": "make test"
},
"engines": {
"node": ">=0.4.0"
},
"dependencies": {
"socket.io": "0.9.2",
"formidable": "1.0.11"
"esprima": "1.0.2"
},
"devDependencies": {},
"optionalDependencies": {},
"engines": {
"node": "*"
},
"directories": {
"lib": "./lib",
"doc": "./doc",
"examples": "node app 1234"
}
"readmeFilename": "Readme.md"
}

@@ -25,10 +25,9 @@ # Aulx [![Build Status](https://travis-ci.org/espadrine/aulx.png)](https://travis-ci.org/espadrine/aulx)

- JS static analysis: a simple algorithm for autocompletion,
- JS dynamic analysis.
- JS Static type inference,
- JS dynamic analysis,
- CSS property autocompletion.
To do:
- A better module system,
- Better static analysis for JS,
- CSS,
- HTML,
- HTML (including inlined CSS and JS autocompletion),
- CoffeeScript, SASS, … We can go crazy with this!

@@ -42,7 +41,6 @@

The main dev entry point is at `entrance/completers.js`.
Project entry point: `entrance/completers.js`.
It uses all completers, each of which has its own directory.
The main entry point for each of those folder is, quite unexpectedly, `main.js`.
They also all have a `test.js` file, which is used for testing.
Completer entry point: `<completer>/main.js` (no surprise there!)

@@ -57,4 +55,4 @@ Building the bundle `aulx.js` is done with this swift command:

Finally, testing completers is either done in batch mode with this other swift
command:
Finally, testing completers is either done in batch mode with yet another
swift command:

@@ -69,1 +67,8 @@ make test

----
*Baked by Thaddée Tyl*.
This work is licensed under the Creative Commons Attribution 3.0 Unported
License. To view a copy of this license, visit
http://creativecommons.org/licenses/by/3.0/.

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc