summaryrefslogtreecommitdiff
path: root/platform/script-debugger/backend/src/org/jetbrains/debugger/sourcemap/SourceResolver.java
blob: 665039507acedb9a8596a288f9a47af39927b0be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package org.jetbrains.debugger.sourcemap;

import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.StandardFileSystems;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Url;
import com.intellij.util.UrlImpl;
import com.intellij.util.Urls;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ObjectIntHashMap;
import com.intellij.util.io.URLUtil;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.io.LocalFileFinder;

import java.util.List;

public class SourceResolver {
  private final List<String> rawSources;
  @Nullable private final List<String> sourcesContent;

  final Url[] canonicalizedSources;
  private final ObjectIntHashMap<Url> canonicalizedSourcesMap;

  private TObjectIntHashMap<String> absoluteLocalPathToSourceIndex;
  // absoluteLocalPathToSourceIndex contains canonical paths too, but this map contains only used (specified in the source map) path
  private String[] sourceIndexToAbsoluteLocalPath;

  public SourceResolver(@NotNull List<String> sourcesUrl, boolean trimFileScheme, @Nullable Url baseFileUrl, @Nullable List<String> sourcesContent) {
    rawSources = sourcesUrl;
    this.sourcesContent = sourcesContent;
    canonicalizedSources = new Url[sourcesUrl.size()];
    canonicalizedSourcesMap = SystemInfo.isFileSystemCaseSensitive
                              ? new ObjectIntHashMap<Url>(canonicalizedSources.length)
                              : new ObjectIntHashMap<Url>(canonicalizedSources.length, Urls.getCaseInsensitiveUrlHashingStrategy());
    for (int i = 0; i < sourcesUrl.size(); i++) {
      String rawSource = sourcesUrl.get(i);
      Url url = canonicalizeUrl(rawSource, baseFileUrl, trimFileScheme, i);
      canonicalizedSources[i] = url;
      canonicalizedSourcesMap.put(url, i);
    }
  }

  public static boolean isAbsolute(@NotNull String path) {
    return !path.isEmpty() && (path.charAt(0) == '/' || (SystemInfo.isWindows && (path.length() > 2 && path.charAt(1) == ':')));
  }

  // see canonicalizeUri kotlin impl and https://trac.webkit.org/browser/trunk/Source/WebCore/inspector/front-end/ParsedURL.js completeURL
  private Url canonicalizeUrl(@NotNull String url, @Nullable Url baseUrl, boolean trimFileScheme, int sourceIndex) {
    if (trimFileScheme && url.startsWith(StandardFileSystems.FILE_PROTOCOL_PREFIX)) {
      return Urls.newLocalFileUrl(FileUtil.toCanonicalPath(VfsUtilCore.toIdeaUrl(url, true).substring(StandardFileSystems.FILE_PROTOCOL_PREFIX.length()), '/'));
    }
    else if (baseUrl == null || url.contains(URLUtil.SCHEME_SEPARATOR) || url.startsWith("data:") || url.startsWith("blob:") || url.startsWith("javascript:")) {
      return Urls.parseEncoded(url);
    }

    String path = url;
    if (url.charAt(0) != '/') {
      String basePath = baseUrl.getPath();
      int lastSlashIndex = basePath.lastIndexOf('/');
      StringBuilder pathBuilder = new StringBuilder();
      if (lastSlashIndex == -1) {
        pathBuilder.append(basePath).append('/');
      }
      else {
        pathBuilder.append(basePath, 0, lastSlashIndex + 1);
      }
      path = pathBuilder.append(url).toString();
    }
    path = FileUtil.toCanonicalPath(path, '/');

    if (baseUrl.getScheme() == null && baseUrl.isInLocalFileSystem()) {
      return Urls.newLocalFileUrl(path);
    }

    // browserify produces absolute path in the local filesystem
    if (isAbsolute(path)) {
      VirtualFile file = LocalFileFinder.findFile(path);
      if (file != null) {
        if (absoluteLocalPathToSourceIndex == null) {
          // must be linked, on iterate original path must be first
          absoluteLocalPathToSourceIndex = createStringIntMap(rawSources.size());
          sourceIndexToAbsoluteLocalPath = new String[rawSources.size()];
        }
        absoluteLocalPathToSourceIndex.put(path, sourceIndex);
        sourceIndexToAbsoluteLocalPath[sourceIndex] = path;
        String canonicalPath = file.getCanonicalPath();
        if (canonicalPath != null && !canonicalPath.equals(path)) {
          absoluteLocalPathToSourceIndex.put(canonicalPath, sourceIndex);
        }
      }
    }
    return new UrlImpl(baseUrl.getScheme(), baseUrl.getAuthority(), path, null);
  }

  @NotNull
  private static ObjectIntHashMap<String> createStringIntMap(int initialCapacity) {
    if (initialCapacity == -1) {
      initialCapacity = 4;
    }
    return SystemInfo.isFileSystemCaseSensitive
           ? new ObjectIntHashMap<String>(initialCapacity)
           : new ObjectIntHashMap<String>(initialCapacity, CaseInsensitiveStringHashingStrategy.INSTANCE);
  }

  @Nullable
  public Url getSource(@NotNull MappingEntry entry) {
    int index = entry.getSource();
    return index < 0 ? null : canonicalizedSources[index];
  }

  @Nullable
  public String getSourceContent(@NotNull MappingEntry entry) {
    if (ContainerUtil.isEmpty(sourcesContent)) {
      return null;
    }

    int index = entry.getSource();
    return index < 0 || index >= sourcesContent.size() ? null : sourcesContent.get(index);
  }

  @Nullable
  public String getRawSource(@NotNull MappingEntry entry) {
    int index = entry.getSource();
    return index < 0 ? null : rawSources.get(index);
  }

  @Nullable
  public String getLocalFilePath(@NotNull MappingEntry entry) {
    final int index = entry.getSource();
    return index < 0 || sourceIndexToAbsoluteLocalPath == null ? null : sourceIndexToAbsoluteLocalPath[index];
  }

  public interface Resolver {
    int resolve(@Nullable VirtualFile sourceFile, @NotNull ObjectIntHashMap<Url> map);
  }

  @Nullable
  public MappingList findMappings(@Nullable VirtualFile sourceFile, @NotNull SourceMap sourceMap, @NotNull Resolver resolver) {
    int index = resolver.resolve(sourceFile, canonicalizedSourcesMap);
    return index < 0 ? null : sourceMap.sourceIndexToMappings[index];
  }

  @Nullable
  public MappingList findMappings(@NotNull List<Url> sourceUrls, @NotNull SourceMap sourceMap, @Nullable VirtualFile sourceFile) {
    for (Url sourceUrl : sourceUrls) {
      int index = canonicalizedSourcesMap.get(sourceUrl.trimParameters());
      if (index != -1) {
        return sourceMap.sourceIndexToMappings[index];
      }
    }

    if (sourceFile != null) {
      MappingList mappings = findByFile(sourceMap, sourceFile);
      if (mappings != null) {
        return mappings;
      }
    }

    return null;
  }

  @Nullable
  private static MappingList getMappingsBySource(@NotNull SourceMap sourceMap, int index) {
    return index == -1 ? null : sourceMap.sourceIndexToMappings[index];
  }

  @Nullable
  private MappingList findByFile(@NotNull SourceMap sourceMap, @NotNull VirtualFile sourceFile) {
    MappingList mappings = null;
    if (absoluteLocalPathToSourceIndex != null && sourceFile.isInLocalFileSystem()) {
      mappings = getMappingsBySource(sourceMap, absoluteLocalPathToSourceIndex.get(sourceFile.getPath()));
      if (mappings == null) {
        String sourceFileCanonicalPath = sourceFile.getCanonicalPath();
        if (sourceFileCanonicalPath != null) {
          mappings = getMappingsBySource(sourceMap, absoluteLocalPathToSourceIndex.get(sourceFileCanonicalPath));
        }
      }
    }

    if (mappings == null) {
      int index = canonicalizedSourcesMap.get(Urls.newFromVirtualFile(sourceFile).trimParameters());
      if (index != -1) {
        return sourceMap.sourceIndexToMappings[index];
      }

      for (int i = 0; i < canonicalizedSources.length; i++) {
        Url url = canonicalizedSources[i];
        if (Urls.equalsIgnoreParameters(url, sourceFile)) {
          return sourceMap.sourceIndexToMappings[i];
        }

        VirtualFile canonicalFile = sourceFile.getCanonicalFile();
        if (canonicalFile != null && !canonicalFile.equals(sourceFile) && Urls.equalsIgnoreParameters(url, canonicalFile)) {
          return sourceMap.sourceIndexToMappings[i];
        }
      }
    }
    return mappings;
  }
}