Coverage Report

Created: 2026-02-27 06:53

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