UNPKG

7.35 kBJavaScriptView Raw
1"use strict";
2/**
3 * Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
4 * This code may only be used under the BSD style license found at
5 * http://polymer.github.io/LICENSE.txt
6 * The complete set of authors may be found at
7 * http://polymer.github.io/AUTHORS.txt
8 * The complete set of contributors may be found at
9 * http://polymer.github.io/CONTRIBUTORS.txt
10 * Code distributed by Google as part of the polymer project is also
11 * subject to an additional IP rights grant found at
12 * http://polymer.github.io/PATENTS.txt
13 */
14Object.defineProperty(exports, "__esModule", { value: true });
15const parse5 = require("parse5");
16const model_1 = require("../model/model");
17/**
18 * Given a position and an HTML document, try to describe what new text typed
19 * at the given position would be.
20 *
21 * Where possible we try to return the ASTNode describing that position, but
22 * sometimes there does not actually exist one. (for a simple case, the empty
23 * string should be interpreted as a text node, but there is no text node in
24 * an empty document, but there would be after the first character was typed).
25 */
26function getLocationInfoForPosition(document, position) {
27 const location = _getLocationInfoForPosition(document.ast, position, document);
28 if (!location) {
29 /** Eh, we're probably in a text node. */
30 return { kind: 'text' };
31 }
32 return location;
33}
34exports.getLocationInfoForPosition = getLocationInfoForPosition;
35function _getLocationInfoForPosition(node, position, document) {
36 const sourceRange = document.sourceRangeForNode(node);
37 const location = node.__location;
38 /**
39 * An HTML5 parser must hallucinate certain nodes, even if they don't exist
40 * in the original source text. e.g. <html> or <body>. So we might have
41 * elements that have no sourceRange (because they don't exist in the text)
42 * but they do have children that do. So we should check those children.
43 */
44 if (!(sourceRange && location)) {
45 return _findLocationInChildren(node, position, document);
46 }
47 if (!model_1.isPositionInsideRange(position, sourceRange)) {
48 // definitively not in this node or any of its children
49 return;
50 }
51 const locationInChildren = _findLocationInChildren(node, position, document);
52 if (locationInChildren) {
53 return locationInChildren;
54 }
55 const attributeLocation = getAttributeLocation(node, position, document, location);
56 if (attributeLocation) {
57 return attributeLocation;
58 }
59 const startTagRange = document.sourceRangeForStartTag(node);
60 const endTagRange = document.sourceRangeForEndTag(node);
61 // If we're in the end tag... we're in the end tag.
62 if (model_1.isPositionInsideRange(position, endTagRange, false)) {
63 return { kind: 'endTag', element: node };
64 }
65 if (startTagRange && model_1.isPositionInsideRange(position, startTagRange, false)) {
66 if (position.line === startTagRange.start.line) {
67 // If the cursor is in the "<my-elem" part of the start tag.
68 if (position.column <=
69 startTagRange.start.column + (node.tagName || '').length + 1) {
70 return { kind: 'tagName', element: node };
71 }
72 }
73 // Otherwise we're in the start tag, but not in the tag name or any
74 // particular attribute, but definitely in the attributes section.
75 return { kind: 'attribute', attribute: null, element: node };
76 }
77 // The edges of a comment aren't part of the comment.
78 if (parse5.treeAdapters.default.isCommentNode(node) &&
79 model_1.isPositionInsideRange(position, sourceRange, false)) {
80 return { kind: 'comment', commentNode: node };
81 }
82 if (parse5.treeAdapters.default.isTextNode(node)) {
83 const parent = node.parentNode;
84 if (parent && parent.tagName === 'script') {
85 return { kind: 'scriptTagContents', textNode: node };
86 }
87 if (parent && parent.tagName === 'style') {
88 return { kind: 'styleTagContents', textNode: node };
89 }
90 return { kind: 'text', textNode: node };
91 }
92 if (model_1.isPositionInsideRange(position, sourceRange, false)) {
93 /**
94 * This is tricky. Consider the position inside an empty element, i.e.
95 * here:
96 * <script>|</script>.
97 *
98 * You can be between the start and end tags, but there won't be a text
99 * node to attach to, but if you started typeing, there would be, so we
100 * want to treat you as though you are.
101 */
102 if (startTagRange && endTagRange &&
103 model_1.comparePositionAndRange(position, startTagRange, false) > 0 &&
104 model_1.comparePositionAndRange(position, endTagRange, false) < 0) {
105 if (node.tagName === 'script') {
106 return { kind: 'scriptTagContents' };
107 }
108 if (node.tagName === 'style') {
109 return { kind: 'styleTagContents' };
110 }
111 return { kind: 'text' };
112 }
113 /**
114 * Ok, we're in this node, we're not in any of its children, but we're not
115 * obviously in any attribute, tagname, start tag, or end tag. We might be
116 * part of a unclosed tag in a mostly empty document. parse5 doesn't give
117 * us much explicit signal in this case, but we can kinda infer it from the
118 * tagName.
119 */
120 if (node.tagName) {
121 if (position.column <=
122 sourceRange.start.column + node.tagName.length + 1) {
123 return { kind: 'tagName', element: node };
124 }
125 return { kind: 'attribute', element: node, attribute: null };
126 }
127 }
128}
129function _findLocationInChildren(node, position, document) {
130 for (const child of node.childNodes || []) {
131 const result = _getLocationInfoForPosition(child, position, document);
132 if (result) {
133 return result;
134 }
135 }
136 if (node.tagName === 'template') {
137 const content = parse5.treeAdapters.default.getTemplateContent(node);
138 const result = _getLocationInfoForPosition(content, position, document);
139 if (result) {
140 return result;
141 }
142 }
143}
144function isElementLocationInfo(location) {
145 const loc = location;
146 return (loc.startTag && loc.endTag) !== undefined;
147}
148/**
149 * If the position is inside of the node's attributes section, return the
150 * correct LocationResult.
151 */
152function getAttributeLocation(node, position, document, location) {
153 /**
154 * TODO(rictic): upstream to @types the fact that regular locations (not just
155 * element locations) can have attrs sometimes.
156 */
157 const attrs = (isElementLocationInfo(location) && location.startTag.attrs) ||
158 location.attrs || {};
159 for (const attrName in attrs) {
160 const range = document.sourceRangeForAttribute(node, attrName);
161 if (model_1.isPositionInsideRange(position, range)) {
162 if (model_1.isPositionInsideRange(position, document.sourceRangeForAttributeValue(node, attrName))) {
163 return { kind: 'attributeValue', attribute: attrName, element: node };
164 }
165 return { kind: 'attribute', attribute: attrName, element: node };
166 }
167 }
168}
169//# sourceMappingURL=ast-from-source-position.js.map
\No newline at end of file