diff --git a/lib/converter/converter.js b/lib/converter/converter.js index c8aa2df7..89d2f45d 100644 --- a/lib/converter/converter.js +++ b/lib/converter/converter.js @@ -18,7 +18,11 @@ const InfoFunction = require('./info-function.js'); // ======================================================================================================== -const getSourceAstInfo = (source, type) => { +const getDistAstInfo = (item, state) => { + const positionMapping = state.positionMapping; + const source = positionMapping.source; + const type = item.type; + const comments = []; const ast = acornLoose.parse(source, { ecmaVersion: 'latest', @@ -26,7 +30,6 @@ const getSourceAstInfo = (source, type) => { // console.log(type, text, start, end); comments.push({ block, - // text, start, end }); @@ -41,7 +44,12 @@ const getSourceAstInfo = (source, type) => { acornWalk.simple(ast, { // Function include FunctionDeclaration, ArrowFunctionExpression, FunctionExpression Function(node) { - functions.push(node); + const functionName = node.id && node.id.name; + functions.push({ + start: node.start, + end: node.end, + functionName + }); } }); functions.sort((a, b) => { @@ -75,25 +83,50 @@ const updateLineComment = (positionMapping, range) => { }; -const initFileCoverage = (positionMapping, type, sourcePath) => { - - // baseLineCount: - // js: 1 (functions include all uncovered) - // css: 0 (ranges include all covered) - let baseLineCount = 0; - if (type === 'js') { - baseLineCount = 1; - } +const getEmptyCoverage = () => { - // v8 + // from ast const ranges = []; - - // istanbul const branches = []; - // parse ast for js and css (only comments) - const source = positionMapping.source; - const astInfo = getSourceAstInfo(source, type); + // from ast + const comments = []; + + // from ast + const functions = []; + const functionMap = new Map(); + + // no comments for now, after ast + const lines = []; + const lineMap = {}; + + const blankCount = 0; + const commentCount = 0; + + return { + + ranges, + branches, + + comments, + + functions, + functionMap, + + lines, + lineMap, + + blankCount, + commentCount + }; +}; + +const initDistFileCoverage = (item, state) => { + + const coverage = getEmptyCoverage(); + + const { type } = item; + const { positionMapping, astInfo } = state; // comments const comments = astInfo.comments; @@ -102,16 +135,16 @@ const initFileCoverage = (positionMapping, type, sourcePath) => { updateLineComment(positionMapping, range); }); - // add all functions - const functionMap = new Map(); - const functions = []; // functions only for js if (type === 'js') { - astInfo.functions.forEach((node) => { - const { start, end } = node; + const functions = []; + const functionMap = new Map(); + astInfo.functions.forEach((fun) => { + const { + start, end, functionName + } = fun; const sLoc = positionMapping.offsetToLocation(start); const eLoc = positionMapping.offsetToLocation(end); - const functionName = node.id && node.id.name; const functionInfo = new InfoFunction(sLoc, eLoc, 0, functionName); const { line, column } = sLoc; @@ -127,15 +160,27 @@ const initFileCoverage = (positionMapping, type, sourcePath) => { functions.push(functionInfo); }); + coverage.functions = functions; + coverage.functionMap = functionMap; + } // add all lines - const lineMap = {}; const lines = []; + const lineMap = {}; // init lines let blankCount = 0; let commentCount = 0; + + // baseLineCount: + // js: 1 (functions include all uncovered) + // css: 0 (ranges include all covered) + let baseLineCount = 0; + if (type === 'js') { + baseLineCount = 1; + } + positionMapping.lines.forEach((it) => { // exclude blank and comment if (it.blank) { @@ -153,24 +198,12 @@ const initFileCoverage = (positionMapping, type, sourcePath) => { lines.push(lineInfo); }); - return { - type, - sourcePath, + coverage.lines = lines; + coverage.lineMap = lineMap; + coverage.blankCount = blankCount; + coverage.commentCount = commentCount; - ranges, - branches, - - lines, - lineMap, - - blankCount, - commentCount, - - functions, - functionMap, - - comments - }; + return coverage; }; // ======================================================================================================== @@ -473,7 +506,10 @@ const addJsCoverage = (coverage, block, range, index, positionMapping) => { }; const addCssCoverage = (coverage, range, positionMapping) => { - const { lineMap } = coverage; + const { lineMap, ranges } = coverage; + + // keep css ranges + ranges.push(range); const { start, end } = range; @@ -488,34 +524,34 @@ const addCssCoverage = (coverage, range, positionMapping) => { // ======================================================================================================== const getDistCoverage = (item, state) => { - const positionMapping = state.positionMapping; - const { type, sourcePath } = item; + const { type } = item; + const { positionMapping } = state; + + // js file if (type === 'js') { - const coverage = initFileCoverage(positionMapping, type, sourcePath); - item.functions.forEach((block) => { + const coverage = initDistFileCoverage(item, state); + state.functions.forEach((block) => { block.ranges.forEach((range, index) => { addJsCoverage(coverage, block, range, index, positionMapping); }); }); - // clean functions after all - if (Util.loggingType !== 'debug') { - delete item.functions; - } - return coverage; } - const coverage = initFileCoverage(positionMapping, type, sourcePath); - item.ranges.forEach((range) => { - coverage.ranges.push(range); + // css file + const coverage = initDistFileCoverage(item, state); + state.ranges.forEach((range) => { addCssCoverage(coverage, range, positionMapping); }); return coverage; }; const unpackDistSource = (item, state) => { + const coverage = getDistCoverage(item, state); + state.coverage = coverage; + const sourcePath = item.sourcePath; const { v8Coverage, istanbulCoverage } = getFileCoverage(coverage, sourcePath); state.coverageData[sourcePath] = istanbulCoverage; @@ -526,19 +562,22 @@ const unpackDistSource = (item, state) => { // ======================================================================================================== -const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => { +const decodeSourceMappings = async (item, state, originalDecodedMap) => { + + const generatedPositionMapping = state.positionMapping; + + const { sources, mappings } = state.sourceMap; - const decodedList = await decodeMappings(sourceMap.mappings); + const decodedList = await decodeMappings(mappings); - const originalIndexMap = new Map(); - sourceMap.sources.forEach((item, i) => { - originalIndexMap.set(i, []); + sources.forEach((source, i) => { + originalDecodedMap.set(i, []); }); const allDecodedMappings = []; let generatedIndex = 0; decodedList.forEach((segments, generatedLine) => { - let item = null; + let info = null; segments.forEach((segment) => { const [generatedColumn, sourceIndex, originalLine, originalColumn] = segment; const generatedOffset = generatedPositionMapping.locationToOffset({ @@ -547,7 +586,7 @@ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => { column: generatedColumn }); - item = { + info = { generatedOffset, generatedLine, generatedColumn, @@ -558,22 +597,22 @@ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => { originalColumn }; - allDecodedMappings.push(item); + allDecodedMappings.push(info); generatedIndex += 1; if (typeof sourceIndex === 'undefined') { return; } - originalIndexMap.get(sourceIndex).push(item); + originalDecodedMap.get(sourceIndex).push(info); }); // line last one - if (item) { - const line = generatedPositionMapping.getLine(item.generatedLine + 1); + if (info) { + const line = generatedPositionMapping.getLine(info.generatedLine + 1); // last column - item.generatedEndOffset = item.generatedOffset + (line.length - item.generatedColumn); + info.generatedEndOffset = info.generatedOffset + (line.length - info.generatedColumn); } }); @@ -583,16 +622,12 @@ const decodeSourceMappings = async (sourceMap, generatedPositionMapping) => { // return a.generatedOffset - b.generatedOffset; // }); - return { - allDecodedMappings, - originalIndexMap - }; - + return allDecodedMappings; }; -const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMapping) => { +const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, positionMapping) => { // all mappings for the original file sorted - const decodedMappings = originalIndexMap.get(sourceIndex); + const decodedMappings = originalDecodedMap.get(sourceIndex); if (!decodeMappings) { return []; @@ -618,19 +653,20 @@ const getOriginalDecodedMappings = (originalIndexMap, sourceIndex, positionMappi return decodedMappings; }; -const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) => { +const initOriginalList = (item, state, originalDecodedMap, options) => { // source filter - const { sources, sourcesContent } = sourceMap; - let sourceFilter = options.sourceFilter; if (typeof sourceFilter !== 'function') { sourceFilter = () => true; } + const fileSources = state.fileSources; + // create original content mappings - const map = new Map(); + const originalMap = new Map(); + const { sources, sourcesContent } = state.sourceMap; sources.forEach((sourcePath, sourceIndex) => { // filter @@ -650,10 +686,10 @@ const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) => const positionMapping = new PositionMapping(sourceContent); - const decodedMappings = getOriginalDecodedMappings(originalIndexMap, sourceIndex, positionMapping); + const decodedMappings = getOriginalDecodedMappings(originalDecodedMap, sourceIndex, positionMapping); // unpacked file always is js - const coverage = initFileCoverage(positionMapping, 'js', sourcePath); + const coverage = getEmptyCoverage(); const type = getSourceType(sourcePath); @@ -666,54 +702,16 @@ const initOriginalList = (sourceMap, originalIndexMap, fileSources, options) => coverage }; - map.set(sourceIndex, originalState); + originalMap.set(sourceIndex, originalState); }); - return map; + return originalMap; }; -const unpackSourceMap = async (item, state, options) => { - const sourceMap = item.sourceMap; - const generatedPositionMapping = state.positionMapping; - const distFile = sourceMap.file || path.basename(item.sourcePath); +const generateOriginalList = (item, state, originalMap) => { - // keep original urls - const fileUrls = {}; - const sourcePathHandler = options.sourcePath; - initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler); - - // decode mappings for each original file - const time_start_decode = Date.now(); - const { allDecodedMappings, originalIndexMap } = await decodeSourceMappings(sourceMap, generatedPositionMapping); - // only debug level - Util.logTime(`decode source mappings ${distFile}`, time_start_decode); - - // filter original list and init list - const fileSources = state.fileSources; - const originalMap = initOriginalList(sourceMap, originalIndexMap, fileSources, options); - - originalIndexMap.clear(); - - const generatedState = { - decodedMappings: allDecodedMappings, - positionMapping: generatedPositionMapping - }; - - // const time_start_mapping = Date.now(); - item.functions.forEach((block) => { - block.ranges.forEach((range, index) => { - - const result = findOriginalRange(range, generatedState, originalMap); - if (!result) { - return; - } - - const { originalRange, originalState } = result; - const { coverage, positionMapping } = originalState; - addJsCoverage(coverage, block, originalRange, index, positionMapping); - - }); - }); + const { fileUrls, sourceMap } = state; + const distFile = sourceMap.file || path.basename(item.sourcePath); // collect original files const sourceList = []; @@ -748,7 +746,73 @@ const unpackSourceMap = async (item, state, options) => { sourceList.push(sourceItem); }); - state.sourceList = sourceList; + return sourceList; +}; + +const unpackSourceMap = async (item, state, options) => { + + const { type, sourcePath } = item; + const sourceMap = state.sourceMap; + + // keep original urls + const fileUrls = {}; + const sourcePathHandler = options.sourcePath; + initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler); + state.fileUrls = fileUrls; + + // =============================================== + // decode mappings for each original file + const time_start_decode = Date.now(); + const originalDecodedMap = new Map(); + // for find-original-range + state.decodedMappings = await decodeSourceMappings(item, state, originalDecodedMap); + // only debug level + Util.logTime(`decode source mappings: ${sourcePath}`, time_start_decode); + + // filter original list and init list + const originalMap = initOriginalList(item, state, originalDecodedMap, options); + + originalDecodedMap.clear(); + + // =============================================== + + const astInfo = state.astInfo; + + // comment count //TODO + astInfo.comments.forEach((comment) => { + + }); + + // const time_start_mapping = Date.now(); + if (type === 'js') { + + // function count //TODO + astInfo.functions.forEach((fun) => { + + }); + + // v8 coverage + state.functions.forEach((block) => { + block.ranges.forEach((range, index) => { + const result = findOriginalRange(range, state, originalMap); + if (!result) { + return; + } + + const { originalRange, originalState } = result; + const { coverage, positionMapping } = originalState; + addJsCoverage(coverage, block, originalRange, index, positionMapping); + + }); + }); + + } else { + // support css later + // current css no sourceMap, so never come in + } + + // collect list + state.sourceList = generateOriginalList(item, state, originalMap); }; @@ -756,10 +820,13 @@ const unpackSourceMap = async (item, state, options) => { const unpackDistFile = async (item, state, options) => { - if (item.sourceMap) { + // ast for dist file + state.astInfo = getDistAstInfo(item, state); + + if (state.sourceMap) { if (Util.loggingType === 'debug') { // js self - unpackDistSource(item, state, options); + unpackDistSource(item, state); } else { item.dedupe = true; } @@ -767,13 +834,10 @@ const unpackDistFile = async (item, state, options) => { // unpack source map await unpackSourceMap(item, state, options); - // remove sourceMap - delete item.sourceMap; - } else { // css/js self - unpackDistSource(item, state, options); + unpackDistSource(item, state); } @@ -807,7 +871,9 @@ const convertV8List = async (v8list, options) => { for (const item of v8list) { // console.log([item.id]); - const { source, sourcePath } = item; + const { + type, source, sourcePath + } = item; const positionMapping = new PositionMapping(source); @@ -821,6 +887,25 @@ const convertV8List = async (v8list, options) => { positionMapping }; + // move functions and ranges + if (type === 'js') { + state.functions = item.functions; + // clean functions after all + if (Util.loggingType !== 'debug') { + delete item.functions; + } + } else { + state.ranges = item.ranges; + // no need remove for css + } + + // move sourceMap + const sourceMap = item.sourceMap; + if (sourceMap) { + state.sourceMap = sourceMap; + delete item.sourceMap; + } + await unpackDistFile(item, state, options); // merge state diff --git a/lib/converter/find-original-range.js b/lib/converter/find-original-range.js index e3737fa0..8074d28c 100644 --- a/lib/converter/find-original-range.js +++ b/lib/converter/find-original-range.js @@ -24,9 +24,9 @@ const findMapping = (list, offset) => { }; -const findOffsetMapping = (generatedState, offset) => { +const findOffsetMapping = (state, offset) => { - const decodedMappings = generatedState.decodedMappings; + const decodedMappings = state.decodedMappings; // possible no length if (!decodedMappings.length) { @@ -69,8 +69,8 @@ const findNextOriginalDiffMapping = (originalState, mapping) => { } }; -const findNextGeneratedDiffMapping = (generatedState, mapping) => { - const decodedMappings = generatedState.decodedMappings; +const findNextGeneratedDiffMapping = (state, mapping) => { + const decodedMappings = state.decodedMappings; const i = mapping.generatedIndex + 1; const l = decodedMappings.length; if (i < l) { @@ -78,7 +78,7 @@ const findNextGeneratedDiffMapping = (generatedState, mapping) => { } }; -const getGeneratedText = (mapping, generatedState) => { +const getGeneratedText = (mapping, state) => { const generatedText = mapping.generatedText; if (typeof generatedText === 'string') { @@ -87,8 +87,8 @@ const getGeneratedText = (mapping, generatedState) => { let text = ''; - const { positionMapping } = generatedState; - const nextMapping = findNextGeneratedDiffMapping(generatedState, mapping); + const positionMapping = state.positionMapping; + const nextMapping = findNextGeneratedDiffMapping(state, mapping); if (nextMapping) { text = positionMapping.getSlice(mapping.generatedOffset, nextMapping.generatedOffset); } else { @@ -326,7 +326,7 @@ const getOriginalEndPosition = (originalText, generatedText, generatedPos) => { // ======================================================================================================== -const fixStartColumn = (startMapping, range, generatedState, originalState) => { +const fixStartColumn = (startMapping, range, state, originalState) => { // exact matched no need fix if (startMapping.exact) { // originalColumn is the column @@ -337,7 +337,7 @@ const fixStartColumn = (startMapping, range, generatedState, originalState) => { const originalColumn = startMapping.originalColumn; const originalText = getOriginalText(startMapping, originalState); - const generatedText = getGeneratedText(startMapping, generatedState); + const generatedText = getGeneratedText(startMapping, state); // actual generatedOffset < range startOffset const generatedPos = range.startOffset - startMapping.generatedOffset; @@ -361,7 +361,7 @@ const fixStartColumn = (startMapping, range, generatedState, originalState) => { // ======================================================================================================== -const fixEndColumn = (endMapping, range, startMapping, generatedState, originalState) => { +const fixEndColumn = (endMapping, range, startMapping, state, originalState) => { const originalColumn = endMapping.originalColumn; @@ -390,7 +390,7 @@ const fixEndColumn = (endMapping, range, startMapping, generatedState, originalS } - const generatedText = getGeneratedText(endMapping, generatedState); + const generatedText = getGeneratedText(endMapping, state); // actual generatedOffset < range endOffset const generatedPos = range.endOffset - endMapping.generatedOffset; @@ -429,12 +429,12 @@ const isOffsetCrossLine = (startMapping, offset) => { return false; }; -const findStartMapping = (range, generatedState, originalMap) => { +const findStartMapping = (range, state, originalMap) => { // startOffset: inclusive const startOffset = range.startOffset; - const startMapping = findOffsetMapping(generatedState, startOffset); + const startMapping = findOffsetMapping(state, startOffset); if (!startMapping) { return; } @@ -444,7 +444,7 @@ const findStartMapping = (range, generatedState, originalMap) => { // but end mapping is exclusive and offset do -1, possible no mapping found, do not check it if (isOffsetCrossLine(startMapping, startOffset)) { // try next mapping if its offset in the range - const nextMapping = findNextGeneratedDiffMapping(generatedState, startMapping); + const nextMapping = findNextGeneratedDiffMapping(state, startMapping); if (!nextMapping) { return; } @@ -484,13 +484,13 @@ const findStartMapping = (range, generatedState, originalMap) => { }; -const findEndMapping = (range, generatedState, startMapping) => { +const findEndMapping = (range, state, startMapping) => { // endOffset: exclusive const endOffset = range.endOffset; // there could be some comments before end mapping even exact matched - const endMapping = findOffsetMapping(generatedState, endOffset - 1); + const endMapping = findOffsetMapping(state, endOffset - 1); if (!endMapping) { return; } @@ -501,7 +501,7 @@ const findEndMapping = (range, generatedState, startMapping) => { } // still exclusive - const exclusiveMapping = findOffsetMapping(generatedState, endOffset); + const exclusiveMapping = findOffsetMapping(state, endOffset); if (exclusiveMapping && exclusiveMapping.originalOffset === endMapping.originalOffset) { endMapping.exclusive = true; } @@ -509,12 +509,12 @@ const findEndMapping = (range, generatedState, startMapping) => { return endMapping; }; -const findOriginalRange = (range, generatedState, originalMap) => { +const findOriginalRange = (range, state, originalMap) => { // startOffset: inclusive // endOffset: exclusive - const startResult = findStartMapping(range, generatedState, originalMap); + const startResult = findStartMapping(range, state, originalMap); if (!startResult) { return; } @@ -531,16 +531,16 @@ const findOriginalRange = (range, generatedState, originalMap) => { // } // ================================================================================== - const endMapping = findEndMapping(range, generatedState, startMapping); + const endMapping = findEndMapping(range, state, startMapping); if (!endMapping) { return; } // fix start - fixStartColumn(startMapping, range, generatedState, originalState); + fixStartColumn(startMapping, range, state, originalState); // fix end - fixEndColumn(endMapping, range, startMapping, generatedState, originalState); + fixEndColumn(endMapping, range, startMapping, state, originalState); const positionMapping = originalState.positionMapping; const originalStart = positionMapping.locationToOffset({