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.
- package/README.md +47 -2
- package/README.zh-Hans.md +47 -2
- package/lib/converter/ast.js +23 -2
- package/lib/converter/collect-source-maps.js +33 -12
- package/lib/converter/converter.js +84 -15
- package/lib/converter/css-coverage.js +76 -0
- package/lib/converter/find-original-range.js +211 -3
- package/lib/converter/untested.js +4 -6
- package/lib/packages/monocart-coverage-assets.js +1 -1
- package/lib/packages/monocart-coverage-converter.js +11 -11
- package/lib/packages/monocart-coverage-vendor.js +1 -1
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -3
- package/lib/platform/share.js +7 -0
- package/lib/utils/source-path.js +21 -6
- package/lib/utils/util.js +6 -1
- package/package.json +161 -153
- package/skills/monocart-coverage-reports/SKILL.md +2 -2
- package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
- package/skills/monocart-coverage-reports/references/collection.md +2 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +7 -3
- package/lib/packages/chunks/chunk-RVOSNMVU.js +0 -2
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
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) => {
|