1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | |
5 | #include <string> |
6 | #include <sstream> |
7 | #include <iostream> |
8 | #include <iomanip> |
9 | |
10 | #include "ast.hpp" |
11 | #include "json.hpp" |
12 | #include "context.hpp" |
13 | #include "position.hpp" |
14 | #include "source_map.hpp" |
15 | |
16 | namespace Sass { |
17 | SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin" ) { } |
18 | SourceMap::SourceMap(const sass::string& file) : current_position(0, 0, 0), file(file) { } |
19 | |
20 | sass::string SourceMap::render_srcmap(Context &ctx) { |
21 | |
22 | const bool include_sources = ctx.c_options.source_map_contents; |
23 | const sass::vector<sass::string> links = ctx.srcmap_links; |
24 | const sass::vector<Resource>& sources(ctx.resources); |
25 | |
26 | JsonNode* json_srcmap = json_mkobject(); |
27 | |
28 | json_append_member(object: json_srcmap, key: "version" , value: json_mknumber(n: 3)); |
29 | |
30 | const char *file_name = file.c_str(); |
31 | JsonNode *json_file_name = json_mkstring(s: file_name); |
32 | json_append_member(object: json_srcmap, key: "file" , value: json_file_name); |
33 | |
34 | // pass-through sourceRoot option |
35 | if (!ctx.source_map_root.empty()) { |
36 | JsonNode* root = json_mkstring(s: ctx.source_map_root.c_str()); |
37 | json_append_member(object: json_srcmap, key: "sourceRoot" , value: root); |
38 | } |
39 | |
40 | JsonNode *json_sources = json_mkarray(); |
41 | for (size_t i = 0; i < source_index.size(); ++i) { |
42 | sass::string source(links[source_index[i]]); |
43 | if (ctx.c_options.source_map_file_urls) { |
44 | source = File::rel2abs(path: source); |
45 | // check for windows abs path |
46 | if (source[0] == '/') { |
47 | // ends up with three slashes |
48 | source = "file://" + source; |
49 | } else { |
50 | // needs an additional slash |
51 | source = "file:///" + source; |
52 | } |
53 | } |
54 | const char* source_name = source.c_str(); |
55 | JsonNode *json_source_name = json_mkstring(s: source_name); |
56 | json_append_element(array: json_sources, element: json_source_name); |
57 | } |
58 | json_append_member(object: json_srcmap, key: "sources" , value: json_sources); |
59 | |
60 | if (include_sources && source_index.size()) { |
61 | JsonNode *json_contents = json_mkarray(); |
62 | for (size_t i = 0; i < source_index.size(); ++i) { |
63 | const Resource& resource(sources[source_index[i]]); |
64 | JsonNode *json_content = json_mkstring(s: resource.contents); |
65 | json_append_element(array: json_contents, element: json_content); |
66 | } |
67 | json_append_member(object: json_srcmap, key: "sourcesContent" , value: json_contents); |
68 | } |
69 | |
70 | JsonNode *json_names = json_mkarray(); |
71 | // so far we have no implementation for names |
72 | // no problem as we do not alter any identifiers |
73 | json_append_member(object: json_srcmap, key: "names" , value: json_names); |
74 | |
75 | sass::string mappings = serialize_mappings(); |
76 | JsonNode *json_mappings = json_mkstring(s: mappings.c_str()); |
77 | json_append_member(object: json_srcmap, key: "mappings" , value: json_mappings); |
78 | |
79 | char *str = json_stringify(node: json_srcmap, space: "\t" ); |
80 | sass::string result = sass::string(str); |
81 | free(ptr: str); |
82 | json_delete(node: json_srcmap); |
83 | return result; |
84 | } |
85 | |
86 | sass::string SourceMap::serialize_mappings() { |
87 | sass::string result = "" ; |
88 | |
89 | size_t previous_generated_line = 0; |
90 | size_t previous_generated_column = 0; |
91 | size_t previous_original_line = 0; |
92 | size_t previous_original_column = 0; |
93 | size_t previous_original_file = 0; |
94 | for (size_t i = 0; i < mappings.size(); ++i) { |
95 | const size_t generated_line = mappings[i].generated_position.line; |
96 | const size_t generated_column = mappings[i].generated_position.column; |
97 | const size_t original_line = mappings[i].original_position.line; |
98 | const size_t original_column = mappings[i].original_position.column; |
99 | const size_t original_file = mappings[i].original_position.file; |
100 | |
101 | if (generated_line != previous_generated_line) { |
102 | previous_generated_column = 0; |
103 | if (generated_line > previous_generated_line) { |
104 | result += sass::string(generated_line - previous_generated_line, ';'); |
105 | previous_generated_line = generated_line; |
106 | } |
107 | } |
108 | else if (i > 0) { |
109 | result += "," ; |
110 | } |
111 | |
112 | // generated column |
113 | result += base64vlq.encode(number: static_cast<int>(generated_column) - static_cast<int>(previous_generated_column)); |
114 | previous_generated_column = generated_column; |
115 | // file |
116 | result += base64vlq.encode(number: static_cast<int>(original_file) - static_cast<int>(previous_original_file)); |
117 | previous_original_file = original_file; |
118 | // source line |
119 | result += base64vlq.encode(number: static_cast<int>(original_line) - static_cast<int>(previous_original_line)); |
120 | previous_original_line = original_line; |
121 | // source column |
122 | result += base64vlq.encode(number: static_cast<int>(original_column) - static_cast<int>(previous_original_column)); |
123 | previous_original_column = original_column; |
124 | } |
125 | |
126 | return result; |
127 | } |
128 | |
129 | void SourceMap::prepend(const OutputBuffer& out) |
130 | { |
131 | Offset size(out.smap.current_position); |
132 | for (Mapping mapping : out.smap.mappings) { |
133 | if (mapping.generated_position.line > size.line) { |
134 | throw(std::runtime_error("prepend sourcemap has illegal line" )); |
135 | } |
136 | if (mapping.generated_position.line == size.line) { |
137 | if (mapping.generated_position.column > size.column) { |
138 | throw(std::runtime_error("prepend sourcemap has illegal column" )); |
139 | } |
140 | } |
141 | } |
142 | // adjust the buffer offset |
143 | prepend(offset: Offset(out.buffer)); |
144 | // now add the new mappings |
145 | VECTOR_UNSHIFT(mappings, out.smap.mappings); |
146 | } |
147 | |
148 | void SourceMap::append(const OutputBuffer& out) |
149 | { |
150 | append(offset: Offset(out.buffer)); |
151 | } |
152 | |
153 | void SourceMap::prepend(const Offset& offset) |
154 | { |
155 | if (offset.line != 0 || offset.column != 0) { |
156 | for (Mapping& mapping : mappings) { |
157 | // move stuff on the first old line |
158 | if (mapping.generated_position.line == 0) { |
159 | mapping.generated_position.column += offset.column; |
160 | } |
161 | // make place for the new lines |
162 | mapping.generated_position.line += offset.line; |
163 | } |
164 | } |
165 | if (current_position.line == 0) { |
166 | current_position.column += offset.column; |
167 | } |
168 | current_position.line += offset.line; |
169 | } |
170 | |
171 | void SourceMap::append(const Offset& offset) |
172 | { |
173 | current_position += offset; |
174 | } |
175 | |
176 | void SourceMap::add_open_mapping(const AST_Node* node) |
177 | { |
178 | const SourceSpan& span(node->pstate()); |
179 | Position from(span.getSrcId(), span.position); |
180 | mappings.push_back(x: Mapping(from, current_position)); |
181 | } |
182 | |
183 | void SourceMap::add_close_mapping(const AST_Node* node) |
184 | { |
185 | const SourceSpan& span(node->pstate()); |
186 | Position to(span.getSrcId(), span.position + span.offset); |
187 | mappings.push_back(x: Mapping(to, current_position)); |
188 | } |
189 | |
190 | SourceSpan SourceMap::remap(const SourceSpan& pstate) { |
191 | for (size_t i = 0; i < mappings.size(); ++i) { |
192 | if ( |
193 | mappings[i].generated_position.file == pstate.getSrcId() && |
194 | mappings[i].generated_position.line == pstate.position.line && |
195 | mappings[i].generated_position.column == pstate.position.column |
196 | ) return SourceSpan(pstate.source, mappings[i].original_position, pstate.offset); |
197 | } |
198 | return SourceSpan(pstate.source, Position(-1, -1, -1), Offset(0, 0)); |
199 | |
200 | } |
201 | |
202 | } |
203 | |