monocart-coverage-reports 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -813,6 +813,44 @@ const checkSourceFileIndexes = (startIndexes, endIndexes) => {
813
813
 
814
814
  };
815
815
 
816
+ const findMappingStartIndex = (mappings, offset) => {
817
+ let low = 0;
818
+ let high = mappings.length;
819
+ while (low < high) {
820
+ const mid = Math.floor((low + high) / 2);
821
+ if (mappings[mid].generatedOffset < offset) {
822
+ low = mid + 1;
823
+ } else {
824
+ high = mid;
825
+ }
826
+ }
827
+ return low;
828
+ };
829
+
830
+ const isFilteredRange = (start, end, mappingInfo, state) => {
831
+ const filtered = state.filteredSourceIndexes;
832
+ if (state.js || !filtered?.size) {
833
+ return false;
834
+ }
835
+ // A mapping after the exclusive end can belong to the next rule. Only
836
+ // inspect the active start segment and mappings inside this range.
837
+ const [startMapping] = [].concat(mappingInfo.startMappings);
838
+ if (!filtered.has(startMapping.sourceIndex)) {
839
+ return false;
840
+ }
841
+
842
+ const mappings = state.decodedMappings;
843
+ const low = findMappingStartIndex(mappings, start);
844
+ // Excluded endpoints alone are not enough: a retained source may have
845
+ // mappings in the middle of a rule that combines multiple source files.
846
+ for (let i = low; i < mappings.length && mappings[i].generatedOffset < end; i++) {
847
+ if (!filtered.has(mappings[i].sourceIndex)) {
848
+ return false;
849
+ }
850
+ }
851
+ return true;
852
+ };
853
+
816
854
  const getOriginalState = (start, end, state, originalMap) => {
817
855
 
818
856
  const mappingInfo = getMappingInfo(start, end, state);
@@ -829,6 +867,7 @@ const getOriginalState = (start, end, state, originalMap) => {
829
867
  if (!results) {
830
868
  return {
831
869
  error: true,
870
+ filtered: isFilteredRange(start, end, mappingInfo, state),
832
871
  errors: [`invalid source indexes: ${EC.yellow(`${startIndexes} ~ ${endIndexes}`)}`]
833
872
  };
834
873
  }
@@ -845,6 +884,7 @@ const getOriginalState = (start, end, state, originalMap) => {
845
884
  if (!originalState) {
846
885
  return {
847
886
  error: true,
887
+ filtered: state.filteredSourceIndexes?.has(sourceIndex) && isFilteredRange(start, end, mappingInfo, state),
848
888
  errors: [`not found original file: ${EC.yellow(sourceIndex)}`]
849
889
  };
850
890
  }
@@ -904,7 +944,167 @@ const getMappingInfo = (start, end, state) => {
904
944
  };
905
945
 
906
946
 
907
- const findOriginalRange = (start, end, state, originalMap, options = {}) => {
947
+ // CSS uses the shared mapping lookup, but owns its endpoint repair and validation.
948
+ const getCssOriginalEndPosition = (cache, state) => {
949
+ const { end, endMappings } = cache;
950
+ if (!Array.isArray(endMappings)) {
951
+ // Synthetic line ends are already exclusive, unlike real token mappings.
952
+ if (endMappings.end) {
953
+ return {
954
+ originalEnd: endMappings.originalOffset
955
+ };
956
+ }
957
+ return getOriginalEndPosition(cache, state);
958
+ }
959
+
960
+ const exclusiveMappings = findMapping(state.decodedMappings, end);
961
+ if (Array.isArray(exclusiveMappings) && !cache.crossEnd) {
962
+ const [mapping] = exclusiveMappings;
963
+ if (mapping.end && mapping.generatedOffset === end - 1) {
964
+ return {
965
+ originalEnd: mapping.originalOffset
966
+ };
967
+ }
968
+ }
969
+ return getOriginalEndPosition(cache, state);
970
+ };
971
+
972
+ const isValidCssOriginalRange = (start, end, length) => Number.isInteger(start) && Number.isInteger(end)
973
+ && start >= 0 && start < end && end <= length;
974
+
975
+ const getCssRangeMappingError = (start, end, state, cache) => {
976
+ // Matching endpoints do not guarantee a contiguous rule: a Sass mixin
977
+ // body can map to another file or an earlier location in the same file.
978
+ const mappings = state.decodedMappings;
979
+ const low = findMappingStartIndex(mappings, start);
980
+ for (let i = low; i < mappings.length && mappings[i].generatedOffset < end; i++) {
981
+ const mapping = mappings[i];
982
+ if (mapping.sourceIndex !== cache.sourceIndex) {
983
+ return {
984
+ error: true,
985
+ errors: ['cross-source CSS range']
986
+ };
987
+ }
988
+ // Synthetic line ends describe whole lines, not actual mapped tokens.
989
+ // Do not validate against the inferred end: indentation-based sources
990
+ // can map a generated closing brace back to the selector's line end.
991
+ if (!mapping.end && mapping.originalOffset < cache.originalStart) {
992
+ return {
993
+ error: true,
994
+ errors: ['mapped token before original CSS range']
995
+ };
996
+ }
997
+ }
998
+ };
999
+
1000
+ const mapCssOriginalRange = (start, end, state, originalMap, options) => {
1001
+ const cache = getOriginalState(start, end, state, originalMap);
1002
+ if (cache.error) {
1003
+ return cache;
1004
+ }
1005
+ const { originalStart, originalName } = getOriginalStartPosition(cache, state, options);
1006
+ cache.originalStart = originalStart;
1007
+ const mappingError = getCssRangeMappingError(start, end, state, cache);
1008
+ if (mappingError) {
1009
+ return mappingError;
1010
+ }
1011
+ const endResult = getCssOriginalEndPosition(cache, state);
1012
+ if (endResult.error) {
1013
+ return endResult;
1014
+ }
1015
+ const { originalEnd } = endResult;
1016
+ const { originalState } = cache;
1017
+ if (!isValidCssOriginalRange(originalStart, originalEnd, originalState.source.length)) {
1018
+ return {
1019
+ error: true,
1020
+ errors: [`invalid original range: ${EC.yellow(`${originalStart} ~ ${originalEnd}`)}`]
1021
+ };
1022
+ }
1023
+ if (originalState.locator.lineParser.commentParser.isComment(originalStart, originalEnd)) {
1024
+ return {
1025
+ error: true,
1026
+ errors: ['the range in a original comment']
1027
+ };
1028
+ }
1029
+ return {
1030
+ start: originalStart,
1031
+ end: originalEnd,
1032
+ name: originalName,
1033
+ originalState
1034
+ };
1035
+ };
1036
+
1037
+ const skipCssTrivia = (originalState, offset, direction) => {
1038
+ const { source, locator } = originalState;
1039
+ const { commentParser } = locator.lineParser;
1040
+ while (offset >= 0 && offset < source.length) {
1041
+ if ((/\s/).test(source[offset])) {
1042
+ offset += direction;
1043
+ continue;
1044
+ }
1045
+ if (commentParser.isComment(offset, offset + 1)) {
1046
+ const comment = commentParser.findComment(offset);
1047
+ offset = direction > 0 ? comment.end : comment.start - 1;
1048
+ continue;
1049
+ }
1050
+ break;
1051
+ }
1052
+ return offset;
1053
+ };
1054
+
1055
+ const fixCssOriginalRange = (result, state, end) => {
1056
+ if (result.error || result.filtered || state.source[end - 1] !== '}') {
1057
+ return result;
1058
+ }
1059
+ const { originalState } = result;
1060
+ const { source } = originalState;
1061
+ // A synthetic endpoint can include whitespace or comments after a brace
1062
+ // already mapped. Do not mistake the next enclosing brace for this rule's.
1063
+ const previousToken = skipCssTrivia(originalState, result.end - 1, -1);
1064
+ if (source[previousToken] === '}') {
1065
+ return result;
1066
+ }
1067
+ // Sass can omit both closing-brace mappings and intervening line comments.
1068
+ // Extend only when the next non-comment, non-whitespace token is a brace.
1069
+ const nextToken = skipCssTrivia(originalState, result.end, 1);
1070
+ if (source[nextToken] === '}') {
1071
+ return {
1072
+ ... result, end: nextToken + 1
1073
+ };
1074
+ }
1075
+ return result;
1076
+ };
1077
+
1078
+ const findCssOriginalRange = (start, end, state, originalMap, options) => {
1079
+ const { sourcePath, rangeCache } = state;
1080
+ const key = `${start}_${end}_${Boolean(options.fixOriginalRange)}`;
1081
+ if (rangeCache.has(key)) {
1082
+ return rangeCache.get(key);
1083
+ }
1084
+
1085
+ let result = mapCssOriginalRange(start, end, state, originalMap, options);
1086
+ if (options.fixOriginalRange) {
1087
+ // A closing brace may map to a mixin caller in another source. Retry the
1088
+ // body when necessary, then repair using the full generated rule end.
1089
+ if (result.error && !result.filtered && state.source[end - 1] === '}') {
1090
+ result = mapCssOriginalRange(start, end - 1, state, originalMap, options);
1091
+ }
1092
+ result = fixCssOriginalRange(result, state, end);
1093
+ }
1094
+ if (result.error) {
1095
+ result = {
1096
+ ... result, start, end, sourcePath
1097
+ };
1098
+ }
1099
+
1100
+ // Cache the final CSS range, error or deliberate exclusion.
1101
+ rangeCache.set(key, result);
1102
+ return result;
1103
+ };
1104
+
1105
+ // ========================================================================================================
1106
+
1107
+ const findJsOriginalRange = (start, end, state, originalMap, options = {}) => {
908
1108
 
909
1109
  const { sourcePath, rangeCache } = state;
910
1110
 
@@ -913,9 +1113,10 @@ const findOriginalRange = (start, end, state, originalMap, options = {}) => {
913
1113
  return rangeCache.get(key);
914
1114
  }
915
1115
 
916
- const createMappingError = (errors) => {
1116
+ const createMappingError = (errors, filtered) => {
917
1117
  const res = {
918
1118
  error: true,
1119
+ filtered,
919
1120
  start,
920
1121
  end,
921
1122
  sourcePath,
@@ -930,7 +1131,7 @@ const findOriginalRange = (start, end, state, originalMap, options = {}) => {
930
1131
 
931
1132
  const cache = getOriginalState(start, end, state, originalMap);
932
1133
  if (cache.error) {
933
- return createMappingError(cache.errors);
1134
+ return createMappingError(cache.errors, cache.filtered);
934
1135
  }
935
1136
 
936
1137
  const originalStartResult = getOriginalStartPosition(cache, state, options);
@@ -977,4 +1178,11 @@ const findOriginalRange = (start, end, state, originalMap, options = {}) => {
977
1178
 
978
1179
  };
979
1180
 
1181
+ const findOriginalRange = (start, end, state, originalMap, options = {}) => {
1182
+ if (state.js) {
1183
+ return findJsOriginalRange(start, end, state, originalMap, options);
1184
+ }
1185
+ return findCssOriginalRange(start, end, state, originalMap, options);
1186
+ };
1187
+
980
1188
  export { findOriginalRange };
@@ -4,7 +4,9 @@ import { Util } from '../utils/util.js';
4
4
 
5
5
  import { pathToFileURL, fileURLToPath } from 'node:url';
6
6
 
7
- import { normalizeSourcePath, getSourcePathReplacer } from '../utils/source-path.js';
7
+ import {
8
+ normalizeSourcePath, getSourcePathReplacer, getSourceType, cssTypes
9
+ } from '../utils/source-path.js';
8
10
 
9
11
  // ========================================================================================================
10
12
 
@@ -67,11 +69,7 @@ const resolveFileType = (fileType, filePath) => {
67
69
  if (fileType === 'js' || fileType === 'css') {
68
70
  return fileType;
69
71
  }
70
- const extname = path.extname(filePath);
71
- if (['.css', '.scss', '.sass', '.less'].includes(extname)) {
72
- return 'css';
73
- }
74
- return 'js';
72
+ return cssTypes.has(getSourceType(filePath)) ? 'css' : 'js';
75
73
  };
76
74
 
77
75
  const saveUntestedFileSource = async (entryFile, options) => {