Skip to content
This repository was archived by the owner on Sep 6, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/extensions/default/JavaScriptCodeHints/ScopeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ define(function (require, exports, module) {
dir = split.dir,
file = split.file;

var ternPromise = getJumptoDef(dir, file, offset, document.getText());
var ternPromise = getJumptoDef(dir, file, offset, session.getJavascriptText());

return {promise: ternPromise};
}
Expand Down Expand Up @@ -286,7 +286,7 @@ define(function (require, exports, module) {
* @return {jQuery.Promise} - The promise will not complete until the tern
* hints have completed.
*/
function requestHints(session, document, offset) {
function requestHints(session, document) {
var path = document.file.fullPath,
split = HintUtils.splitPath(path),
dir = split.dir,
Expand All @@ -295,12 +295,14 @@ define(function (require, exports, module) {
var $deferredHints = $.Deferred(),
hintPromise,
fnTypePromise,
propsPromise;
propsPromise,
text = session.getJavascriptText(),
offset = session.getOffset();

hintPromise = getTernHints(dir, file, offset, document.getText());
hintPromise = getTernHints(dir, file, offset, text);
var sessionType = session.getType();
if (sessionType.property) {
propsPromise = getTernProperties(dir, file, offset, document.getText());
propsPromise = getTernProperties(dir, file, offset, text);
} else {
var $propsDeferred = $.Deferred();
propsPromise = $propsDeferred.promise();
Expand All @@ -309,7 +311,7 @@ define(function (require, exports, module) {

if (sessionType.showFunctionType) {
// Show function sig
fnTypePromise = getTernFunctionType(dir, file, sessionType.functionCallPos, offset, document.getText());
fnTypePromise = getTernFunctionType(dir, file, sessionType.functionCallPos, offset, text);
} else {
var $fnTypeDeferred = $.Deferred();
fnTypePromise = $fnTypeDeferred.promise();
Expand Down
68 changes: 64 additions & 4 deletions src/extensions/default/JavaScriptCodeHints/Session.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
*
*/

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */
/*global define, brackets, $ */

define(function (require, exports, module) {
"use strict";

var StringMatch = brackets.getModule("utils/StringMatch"),
LanguageManager = brackets.getModule("language/LanguageManager"),
HTMLUtils = brackets.getModule("language/HTMLUtils"),
TokenUtils = brackets.getModule("utils/TokenUtils"),
HintUtils = require("HintUtils"),
ScopeManager = require("ScopeManager");

Expand Down Expand Up @@ -273,18 +276,30 @@ define(function (require, exports, module) {
* function being called
*/
Session.prototype.getType = function () {
function getLexicalState(token) {
if (token.state.lexical) {
// in a javascript file this is just in the state field
return token.state.lexical;
} else if (token.state.localState && token.state.localState.lexical) {
// inline javascript in an html file will have this in
// the localState field
return token.state.localState.lexical;
}
}
var propertyLookup = false,
inFunctionCall = false,
showFunctionType = false,
context = null,
cursor = this.getCursor(),
functionCallPos,
token = this.getToken(cursor);
token = this.getToken(cursor),
lexical;

if (token) {
// if this token is part of a function call, then the tokens lexical info
// will be annotated with "call"
if (token.state.lexical.info === "call") {
lexical = getLexicalState(token);
if (lexical.info === "call") {
inFunctionCall = true;
if (this.getQuery().length > 0) {
inFunctionCall = false;
Expand All @@ -301,7 +316,7 @@ define(function (require, exports, module) {
// and it will prevent us from walking back thousands of lines if something went wrong.
// there is nothing magical about 9 lines, and it can be adjusted if it doesn't seem to be
// working well
var col = token.state.lexical.column,
var col = lexical.column,
line,
e,
found;
Expand Down Expand Up @@ -471,6 +486,51 @@ define(function (require, exports, module) {
}
return hints;
};

/**
* Get the javascript text of the file open in the editor for this Session.
* For a javascript file, this is just the text of the file. For an HTML file,
* this will be only the text in the <script> tags. This is so that we can pass
* just the javascript text to tern, and avoid confusing it with HTML tags, since it
* only knows how to parse javascript.
* @return {string} - the "javascript" text that can be sent to Tern.
*/
Session.prototype.getJavascriptText = function () {
if (LanguageManager.getLanguageForPath(this.editor.document.file.fullPath).getId() === "html") {
// HTML file - need to send back only the bodies of the
// <script> tags
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's code very similar to this in HTMLUtils.findStyleBlocks(). Should we consolidate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterflynn good idea, will do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored HTMLUtils.findStyleBlocks in eztierney@5d7dc76

var text = "",
offset = this.getOffset(),
cursor = this.getCursor(),
editor = this.editor,
scriptBlocks = HTMLUtils.findBlocks(editor, "javascript");

// Add all the javascript text
// For non-javascript blocks we replace everything except for newlines
// with whitespace. This is so that the offset and cursor positions
// we get from the document still work.
// Alternatively we could strip the non-javascript text, and modify the offset,
// and/or cursor, but then we have to remember how to reverse the translation
// to support jump-to-definition
var htmlStart = {line: 0, ch: 0};
scriptBlocks.forEach(function (scriptBlock) {
var start = scriptBlock.start,
end = scriptBlock.end;

// get the preceding html text, and replace it with whitespace
var htmlText = editor.document.getRange(htmlStart, start);
htmlText = htmlText.replace(/./g, " ");

htmlStart = end;
text += htmlText + scriptBlock.text;
});

return text;
} else {
// Javascript file, just return the text
return this.editor.document.getText();
}
};

module.exports = Session;
});
24 changes: 21 additions & 3 deletions src/extensions/default/JavaScriptCodeHints/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ define(function (require, exports, module) {
ExtensionUtils = brackets.getModule("utils/ExtensionUtils"),
StringUtils = brackets.getModule("utils/StringUtils"),
StringMatch = brackets.getModule("utils/StringMatch"),
LanguageManager = brackets.getModule("language/LanguageManager"),
HintUtils = require("HintUtils"),
ScopeManager = require("ScopeManager"),
Session = require("Session"),
Expand Down Expand Up @@ -235,6 +236,18 @@ define(function (require, exports, module) {
}
};

/**
* @return {boolean} - true if the document is a html file
*/
function isHTMLFile(document) {
var languageID = LanguageManager.getLanguageForPath(document.file.fullPath).getId();
return languageID === "html";
}

function isInlineScript(editor) {
return editor.getModeForSelection() === "javascript";
}

/**
* Determine whether hints are available for a given editor context
*
Expand All @@ -244,6 +257,12 @@ define(function (require, exports, module) {
*/
JSHints.prototype.hasHints = function (editor, key) {
if (session && HintUtils.hintableKey(key)) {

if (isHTMLFile(session.editor.document)) {
if (!isInlineScript(session.editor)) {
return false;
}
}
var cursor = session.getCursor(),
token = session.getToken(cursor);

Expand Down Expand Up @@ -292,8 +311,7 @@ define(function (require, exports, module) {
// Compute fresh hints if none exist, or if the session
// type has changed since the last hint computation
if (this.needNewHints(session)) {
var offset = session.getOffset(),
scopeResponse = ScopeManager.requestHints(session, session.editor.document, offset);
var scopeResponse = ScopeManager.requestHints(session, session.editor.document);

if (scopeResponse.hasOwnProperty("promise")) {
var $deferredHints = $.Deferred();
Expand Down Expand Up @@ -541,7 +559,7 @@ define(function (require, exports, module) {
installEditorListeners(EditorManager.getActiveEditor());

var jsHints = new JSHints();
CodeHintManager.registerHintProvider(jsHints, [HintUtils.LANGUAGE_ID], 0);
CodeHintManager.registerHintProvider(jsHints, [HintUtils.LANGUAGE_ID, "html"], 0);

// for unit testing
exports.getSession = getSession;
Expand Down
35 changes: 35 additions & 0 deletions src/extensions/default/JavaScriptCodeHints/test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test file for codehints in .html file</title>
</head>
<body>
<script src="file1.js"></script>
<script>
var x = "hi";
</script>
some stuff

<input type="button">

<div class="content">
</div>
<script>
function foo(a){
return "hi";
}

</script>

some other stuff
<div class = "content">
</div>

<script>
foo(10);

</script>

</body>
</html>
64 changes: 62 additions & 2 deletions src/extensions/default/JavaScriptCodeHints/unittests.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ define(function (require, exports, module) {

var extensionPath = FileUtils.getNativeModuleDirectoryPath(module),
testPath = extensionPath + "/test/file1.js",
testHtmlPath = extensionPath + "/test/index.html",
testDoc = null,
testEditor;

Expand Down Expand Up @@ -76,7 +77,7 @@ define(function (require, exports, module) {
if (key === undefined) {
key = null;
}

expect(provider.hasHints(testEditor, key)).toBe(true);
return provider.getHints(null);
}
Expand Down Expand Up @@ -324,7 +325,6 @@ define(function (require, exports, module) {
DocumentManager.getDocumentForPath(testPath).done(function (doc) {
testDoc = doc;
});

waitsFor(function () {
return testDoc !== null;
}, "Unable to open test document", 10000);
Expand Down Expand Up @@ -779,6 +779,66 @@ define(function (require, exports, module) {
});
});
});

describe("JavaScript Code Hinting in a HTML file", function () {

beforeEach(function () {

DocumentManager.getDocumentForPath(testHtmlPath).done(function (doc) {
testDoc = doc;
});

waitsFor(function () {
return testDoc !== null;
}, "Unable to open test document", 10000);

// create Editor instance (containing a CodeMirror instance)
runs(function () {
testEditor = createMockEditor(testDoc);
JSCodeHints.initializeSession(testEditor, false);
});
});

afterEach(function () {
// The following call ensures that the document is reloaded
// from disk before each test
DocumentManager.closeAll();

SpecRunnerUtils.destroyMockEditor(testDoc);
testEditor = null;
testDoc = null;
});

it("basic codehints in html file", function () {
var start = { line: 30, ch: 9 },
end = { line: 30, ch: 11 };

testDoc.replaceRange("x.", start, start);
testEditor.setCursorPos(end);
var hintObj = expectHints(JSCodeHints.jsHintProvider);
runs(function () {
hintsPresentOrdered(hintObj, ["charAt", "charCodeAt", "concat", "indexOf"]);
});
});

it("function type hint in html file", function () {
var start = { line: 29, ch: 12 };

testEditor.setCursorPos(start);
var hintObj = expectHints(JSCodeHints.jsHintProvider);
runs(function () {
hintsPresentExact(hintObj, ["foo(a: number) -> string"]);
});
});

it("jump-to-def in html file", function () {
var start = { line: 29, ch: 10 };

testEditor.setCursorPos(start);
runs(function () {
editorJumped({line: 18, ch: 20});
});
});
});
});
});
Loading