Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

source-loader: Fix parser imports from prettier #29669

Open
wants to merge 3 commits into
base: next
Choose a base branch
from

Conversation

slax57
Copy link

@slax57 slax57 commented Nov 20, 2024

Closes #29478

What I did

After a lot of investigation, I find out that the source-loader code was not properly updated after bumping prettier to v3.
In particular, it does not apply the following requirement from the upgrade guide:

- import parseTs from 'prettier/plugins/typescript';
+ import * as parseTs from 'prettier/plugins/typescript';

This resulted in an error like the following when attempting to use any of the parsers, javascript, typescript or flow:

TypeError: Cannot read properties of undefined (reading 'parsers')
      at Object.parse3 [as parse] (/home/slax57/workspaces/react-admin-v5/node_modules/@storybook/source-loader/dist/index.js:2:3688)
      at inject (/home/slax57/workspaces/react-admin-v5/node_modules/@storybook/source-loader/dist/index.js:6:1261)
      at readAsObject (/home/slax57/workspaces/react-admin-v5/node_modules/@storybook/source-loader/dist/index.js:14:654)
      at readStory (/home/slax57/workspaces/react-admin-v5/node_modules/@storybook/source-loader/dist/index.js:14:1062)
      ...

This error never made its way up to the user because of the try catch in the javascript, typescript parsers.

try {
return parseTs.parsers.typescript.parse(source);
} catch (error1) {
try {
return JSON.stringify(source);
} catch (error) {
throw error1;
}
}

However, setting the parser to flow will actually throw this error, because for some reason the flow parser does not have the try catch block.

Anyways, fixing the import fixes the parser, which in turn fixes the source-loader and the storysource addon! 🎉

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

We can reuse the sandbox from #29478:

https://stackblitz.com/edit/github-h25xa6?file=.storybook%2Fmain.ts

In this sandbox:

  1. Let it start and run storybook once.
  2. In the integrated terminal, stop the execution of storybook (Ctrl+C)
  3. Run code node_modules/@storybook/source-loader/dist/index.js to edit the transpiled file
  4. In the editor, copy the fixed version of source-loader/dist/index.js (you can get it by building the code on this PR)
  5. Restart storybook: yarn storybook
  6. Check that the Code panel is present, and contains the full code 🎉

For anyone wanting to try out the fix themselves without having to build the source-loader sources, here is the transpiled file (built as of version 8.4.4):

Click me
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var src_exports = {};
__export(src_exports, {
  default: () => src_default,
  extractSource: () => extractSource
});
module.exports = __toCommonJS(src_exports);

// src/build.js
var import_promises = require("fs/promises");

// src/abstract-syntax-tree/generate-helpers.js
var import_csf3 = require("@storybook/csf");
var import_compat = require("es-toolkit/compat");

// src/extract-source.ts
function extractSource(location, lines) {
  const { startBody: start, endBody: end } = location;
  if (start.line === end.line && lines[start.line - 1] !== void 0) {
    return lines[start.line - 1].substring(start.col, end.col);
  }
  const startLine = lines[start.line - 1];
  const endLine = lines[end.line - 1];
  if (startLine === void 0 || endLine === void 0) {
    return null;
  }
  return [
    startLine.substring(start.col),
    ...lines.slice(start.line, end.line - 1),
    endLine.substring(0, end.col)
  ].join("\n");
}

// src/abstract-syntax-tree/parse-helpers.js
var import_csf = require("@storybook/csf");
var STORIES_OF = "storiesOf";
function pushParts(source, parts, from, to) {
  const start = source.slice(from, to);
  parts.push(start);
  const end = source.slice(to);
  parts.push(end);
}
function patchNode(node) {
  if (node.range && node.range.length === 2 && node.start === void 0 && node.end === void 0) {
    const [start, end] = node.range;
    node.start = start;
    node.end = end;
  }
  if (!node.range && node.start !== void 0 && node.end !== void 0) {
    node.range = [node.start, node.end];
  }
  return node;
}
function findTemplate(templateName, program) {
  let template = null;
  program.body.find((node) => {
    let declarations = null;
    if (node.type === "VariableDeclaration") {
      declarations = node.declarations;
    } else if (node.type === "ExportNamedDeclaration" && node.declaration?.type === "VariableDeclaration") {
      declarations = node.declaration.declarations;
    }
    return declarations && declarations.find((decl) => {
      if (decl.type === "VariableDeclarator" && decl.id.type === "Identifier" && decl.id.name === templateName) {
        template = decl.init;
        return true;
      }
      return false;
    });
  });
  return template;
}
function expandBindExpression(node, parent) {
  if (node.type === "CallExpression") {
    const { callee, arguments: bindArguments } = node;
    if (parent.type === "Program" && callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.property.type === "Identifier" && callee.property.name === "bind" && (bindArguments.length === 0 || bindArguments.length === 1 && bindArguments[0].type === "ObjectExpression" && bindArguments[0].properties.length === 0)) {
      const boundIdentifier = callee.object.name;
      const template = findTemplate(boundIdentifier, parent);
      if (template) {
        return template;
      }
    }
  }
  return node;
}
function handleExportedName(storyName, originalNode, parent) {
  const node = expandBindExpression(originalNode, parent);
  const startLoc = {
    col: node.loc.start.column,
    line: node.loc.start.line
  };
  const endLoc = {
    col: node.loc.end.column,
    line: node.loc.end.line
  };
  return {
    [storyName]: {
      startLoc,
      endLoc,
      startBody: startLoc,
      endBody: endLoc
    }
  };
}
function handleADD(node, parent, storiesOfIdentifiers) {
  if (!node.property || !node.property.name || node.property.name !== "add") {
    return {};
  }
  const addArgs = parent.arguments;
  if (!addArgs || addArgs.length < 2) {
    return {};
  }
  let tmp = node.object;
  while (tmp.callee && tmp.callee.object) {
    tmp = tmp.callee.object;
  }
  const framework = tmp.callee && tmp.callee.name && storiesOfIdentifiers[tmp.callee.name];
  const storyName = addArgs[0];
  const body = addArgs[1];
  const lastArg = addArgs[addArgs.length - 1];
  if (storyName.type !== "Literal" && storyName.type !== "StringLiteral") {
    return {};
  }
  if (storyName.value && typeof storyName.value === "string") {
    const key = (0, import_csf.sanitize)(storyName.value);
    let idToFramework;
    if (key && framework) {
      idToFramework = { [key]: framework };
    }
    return {
      toAdd: {
        [key]: {
          // Debug: code: source.slice(storyName.start, lastArg.end),
          startLoc: {
            col: storyName.loc.start.column,
            line: storyName.loc.start.line
          },
          endLoc: {
            col: lastArg.loc.end.column,
            line: lastArg.loc.end.line
          },
          startBody: {
            col: body.loc.start.column,
            line: body.loc.start.line
          },
          endBody: {
            col: body.loc.end.column,
            line: body.loc.end.line
          }
        }
      },
      idToFramework
    };
  }
  return {};
}
function handleSTORYOF(node, parts, source, lastIndex) {
  if (!node.callee || !node.callee.name || node.callee.name !== STORIES_OF) {
    return lastIndex;
  }
  parts.pop();
  pushParts(source, parts, lastIndex, node.end);
  return node.end;
}

// src/abstract-syntax-tree/parsers/parser-flow.js
var import_flow = __toESM(require("prettier/plugins/flow"));
function parse(source) {
  return import_flow.default.parsers.flow.parse(source);
}
function format(source) {
  return import_flow.default.parsers.flow.format(source);
}
var parser_flow_default = {
  parse,
  format
};

// src/abstract-syntax-tree/parsers/parser-js.js
var import_babel = __toESM(require("prettier/plugins/babel"));
function parse2(source) {
  try {
    return import_babel.default.parsers.babel.parse(source);
  } catch (error1) {
    try {
      return JSON.stringify(source);
    } catch (error) {
      throw error1;
    }
  }
}
function format2(source) {
  return import_babel.default.parsers.babel.format(source);
}
var parser_js_default = {
  parse: parse2,
  format: format2
};

// src/abstract-syntax-tree/parsers/parser-ts.js
var import_typescript = require("prettier/plugins/typescript");
function parse3(source) {
  try {
    return import_typescript.parsers.typescript.parse(source);
  } catch (error1) {
    try {
      return JSON.stringify(source);
    } catch (error) {
      throw error1;
    }
  }
}
function format3(source) {
  return import_typescript.parsers.typescript.format(source);
}
var parser_ts_default = {
  parse: parse3,
  format: format3
};

// src/abstract-syntax-tree/parsers/index.js
function getParser(type) {
  if (type === "javascript" || /\.jsx?/.test(type) || !type) {
    return parser_js_default;
  }
  if (type === "typescript" || /\.tsx?/.test(type)) {
    return parser_ts_default;
  }
  if (type === "flow") {
    return parser_flow_default;
  }
  throw new Error(`Parser of type "${type}" is not supported`);
}
var parsers_default = getParser;

// src/abstract-syntax-tree/traverse-helpers.js
var import_csf2 = require("@storybook/csf");
var import_estraverse = __toESM(require("estraverse"));
function splitSTORYOF(ast, source) {
  let lastIndex = 0;
  const parts = [source];
  import_estraverse.default.traverse(ast, {
    fallback: "iteration",
    enter: (node) => {
      patchNode(node);
      if (node.type === "CallExpression") {
        lastIndex = handleSTORYOF(node, parts, source, lastIndex);
      }
    }
  });
  return parts;
}
function isFunctionVariable(declarations, includeExclude) {
  return declarations && declarations.length === 1 && declarations[0].type === "VariableDeclarator" && declarations[0].id && declarations[0].id.name && declarations[0].init && [
    "CallExpression",
    "ArrowFunctionExpression",
    "FunctionExpression",
    "ObjectExpression"
    // CSF3
  ].includes(declarations[0].init.type) && (0, import_csf2.isExportStory)(declarations[0].id.name, includeExclude);
}
function isFunctionDeclaration(declaration, includeExclude) {
  return declaration.type === "FunctionDeclaration" && declaration.id && declaration.id.name && (0, import_csf2.isExportStory)(declaration.id.name, includeExclude);
}
function getDescriptor(metaDeclaration, propertyName) {
  const property = metaDeclaration && metaDeclaration.declaration && metaDeclaration.declaration.properties.find((p) => p.key && p.key.name === propertyName);
  if (!property) {
    return void 0;
  }
  const { type } = property.value;
  switch (type) {
    case "ArrayExpression":
      return property.value.elements.map((t) => {
        if (!["StringLiteral", "Literal"].includes(t.type)) {
          throw new Error(`Unexpected descriptor element: ${t.type}`);
        }
        return t.value;
      });
    case "Literal":
    case "RegExpLiteral":
      return property.value.value;
    default:
      throw new Error(`Unexpected descriptor: ${type}`);
  }
}
function findIncludeExclude(ast) {
  const program = ast && ast.program || ast;
  const metaDeclaration = program && program.body && program.body.find(
    (d) => d.type === "ExportDefaultDeclaration" && d.declaration.type === "ObjectExpression" && (d.declaration.properties || []).length
  );
  const includeStories = getDescriptor(metaDeclaration, "includeStories");
  const excludeStories = getDescriptor(metaDeclaration, "excludeStories");
  return {
    includeStories,
    excludeStories
  };
}
function splitExports(ast, source) {
  const parts = [];
  let lastIndex = 0;
  const includeExclude = findIncludeExclude(ast);
  import_estraverse.default.traverse(ast, {
    fallback: "iteration",
    enter: (node) => {
      patchNode(node);
      const isNamedExport = node.type === "ExportNamedDeclaration" && node.declaration;
      const isFunctionVariableExport = isNamedExport && isFunctionVariable(node.declaration.declarations, includeExclude);
      const isFunctionDeclarationExport = isNamedExport && isFunctionDeclaration(node.declaration, includeExclude);
      if (isFunctionDeclarationExport || isFunctionVariableExport) {
        const functionNode = isFunctionVariableExport ? node.declaration.declarations[0].init : node.declaration;
        parts.push({
          source: source.substring(lastIndex, functionNode.start - 1)
        });
        parts.push({
          source: source.substring(functionNode.start, functionNode.end),
          declaration: {
            isVariableDeclaration: isFunctionVariableExport,
            ident: isFunctionVariableExport ? node.declaration.declarations[0].id.name : functionNode.id.name
          }
        });
        lastIndex = functionNode.end;
      }
    }
  });
  if (source.length > lastIndex + 1) {
    parts.push({ source: source.substring(lastIndex + 1) });
  }
  if (parts.length === 1) {
    return [source];
  }
  return parts;
}
function findAddsMap(ast, storiesOfIdentifiers) {
  const addsMap = {};
  import_estraverse.default.traverse(ast, {
    fallback: "iteration",
    enter: (node, parent) => {
      patchNode(node);
      if (node.type === "MemberExpression") {
        const { toAdd, idToFramework } = handleADD(node, parent, storiesOfIdentifiers);
        Object.assign(addsMap, toAdd);
      }
    }
  });
  return addsMap;
}
function findExportsMap(ast) {
  const addsMap = {};
  import_estraverse.default.traverse(ast, {
    fallback: "iteration",
    enter: (node, parent) => {
      patchNode(node);
      const isNamedExport = node.type === "ExportNamedDeclaration" && node.declaration;
      const isFunctionVariableExport = isNamedExport && node.declaration.declarations && node.declaration.declarations.length === 1 && node.declaration.declarations[0].type === "VariableDeclarator" && node.declaration.declarations[0].id && node.declaration.declarations[0].id.name && node.declaration.declarations[0].init && [
        "CallExpression",
        "ArrowFunctionExpression",
        "FunctionExpression",
        "ObjectExpression"
        // CSF3
      ].includes(node.declaration.declarations[0].init.type);
      const isFunctionDeclarationExport = isNamedExport && node.declaration.type === "FunctionDeclaration" && node.declaration.id && node.declaration.id.name;
      if (isFunctionDeclarationExport || isFunctionVariableExport) {
        const exportDeclaration = isFunctionVariableExport ? node.declaration.declarations[0] : node.declaration;
        const toAdd = handleExportedName(
          exportDeclaration.id.name,
          exportDeclaration.init || exportDeclaration,
          parent
        );
        Object.assign(addsMap, toAdd);
      }
    }
  });
  return addsMap;
}
function popParametersObjectFromDefaultExport(source, ast) {
  let splicedSource = source;
  let parametersSliceOfCode = "";
  let indexWhereToAppend = -1;
  let foundParametersProperty = false;
  import_estraverse.default.traverse(ast, {
    fallback: "iteration",
    enter: (node) => {
      patchNode(node);
      const isDefaultExport = node.type === "ExportDefaultDeclaration";
      let decl = node.declaration;
      if (isDefaultExport && decl?.type === "Identifier") {
        ast.body.forEach((n) => {
          if (n.type === "VariableDeclaration") {
            n.declarations.forEach((d) => {
              if (d.id.name === decl.name) {
                decl = d.init;
              }
            });
          }
        });
      }
      const isObjectExpression = decl?.type === "ObjectExpression";
      const isTsAsExpression = decl?.type === "TSAsExpression";
      const targetNode = isObjectExpression ? decl : decl?.expression;
      if (isDefaultExport && (isObjectExpression || isTsAsExpression) && (targetNode.properties || []).length) {
        const parametersProperty = targetNode.properties.find(
          (p) => p.key && p.key.name === "parameters" && p.value.type === "ObjectExpression"
        );
        foundParametersProperty = !!parametersProperty;
        if (foundParametersProperty) {
          patchNode(parametersProperty.value);
        } else {
          patchNode(targetNode);
        }
        splicedSource = parametersProperty ? source.substring(0, parametersProperty.value.start) + source.substring(parametersProperty.value.end + 1) : splicedSource;
        parametersSliceOfCode = parametersProperty ? source.substring(parametersProperty.value.start, parametersProperty.value.end) : "{}";
        indexWhereToAppend = parametersProperty ? parametersProperty.value.start : targetNode.start + 1;
      }
    }
  });
  return { splicedSource, parametersSliceOfCode, indexWhereToAppend, foundParametersProperty };
}

// src/abstract-syntax-tree/generate-helpers.js
function sanitizeSource(source) {
  return JSON.stringify(source, null, 2).trim().replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
}
function isUglyComment(comment, uglyCommentsRegex) {
  return uglyCommentsRegex.some((regex) => regex.test(comment));
}
function generateSourceWithoutUglyComments(source, { comments, uglyCommentsRegex }) {
  let lastIndex = 0;
  const parts = [source];
  comments.filter((comment) => isUglyComment(comment.value.trim(), uglyCommentsRegex)).map(patchNode).forEach((comment) => {
    parts.pop();
    const start = source.slice(lastIndex, comment.start);
    const end = source.slice(comment.end);
    parts.push(start, end);
    lastIndex = comment.end;
  });
  return parts.join("");
}
function prettifyCode(source, { prettierConfig, parser, filepath }) {
  let config = prettierConfig;
  let foundParser = null;
  if (parser === "flow") {
    foundParser = "flow";
  }
  if (parser === "javascript" || /jsx?/.test(parser)) {
    foundParser = "javascript";
  }
  if (parser === "typescript" || /tsx?/.test(parser)) {
    foundParser = "typescript";
  }
  if (!config.parser) {
    config = {
      ...prettierConfig
    };
  } else if (filepath) {
    config = {
      ...prettierConfig,
      filepath
    };
  } else {
    config = {
      ...prettierConfig
    };
  }
  try {
    return parsers_default(foundParser || "javascript").format(source, config);
  } catch (e) {
    return source;
  }
}
var ADD_PARAMETERS_STATEMENT = ".addParameters({ storySource: { source: __STORY__, locationsMap: __LOCATIONS_MAP__ } })";
var applyExportDecoratorStatement = (part) => part.declaration.isVariableDeclaration ? ` ${part.source};` : ` const ${part.declaration.ident} = ${part.source};`;
function generateSourceWithDecorators(source, ast) {
  const { comments = [] } = ast;
  const partsUsingStoryOfToken = splitSTORYOF(ast, source);
  if (partsUsingStoryOfToken.length > 1) {
    const newSource2 = partsUsingStoryOfToken.join(ADD_PARAMETERS_STATEMENT);
    return {
      storyOfTokenFound: true,
      changed: partsUsingStoryOfToken.length > 1,
      source: newSource2,
      comments
    };
  }
  const partsUsingExports = splitExports(ast, source);
  const newSource = partsUsingExports.map((part, i) => i % 2 === 0 ? part.source : applyExportDecoratorStatement(part)).join("");
  return {
    exportTokenFound: true,
    changed: partsUsingExports.length > 1,
    source: newSource,
    comments
  };
}
function generateSourceWithoutDecorators(source, ast) {
  const { comments = [] } = ast;
  return {
    changed: true,
    source,
    comments
  };
}
function generateAddsMap(ast, storiesOfIdentifiers) {
  return findAddsMap(ast, storiesOfIdentifiers);
}
function generateStoriesLocationsMap(ast, storiesOfIdentifiers) {
  const usingAddsMap = generateAddsMap(ast, storiesOfIdentifiers);
  const addsMap = usingAddsMap;
  if (Object.keys(addsMap).length > 0) {
    return usingAddsMap;
  }
  const usingExportsMap = findExportsMap(ast);
  return usingExportsMap || usingAddsMap;
}
function generateStorySource({ source, ...options }) {
  let storySource = source;
  storySource = generateSourceWithoutUglyComments(storySource, options);
  storySource = prettifyCode(storySource, options);
  return storySource;
}
function transformLocationMapToIds(parameters) {
  if (!parameters?.locationsMap) {
    return parameters;
  }
  const locationsMap = (0, import_compat.mapKeys)(parameters.locationsMap, (_value, key) => {
    return (0, import_csf3.sanitize)((0, import_csf3.storyNameFromExport)(key));
  });
  return { ...parameters, locationsMap };
}
function generateSourcesInExportedParameters(source, ast, additionalParameters) {
  const { splicedSource, parametersSliceOfCode, indexWhereToAppend, foundParametersProperty } = popParametersObjectFromDefaultExport(source, ast);
  if (indexWhereToAppend !== -1) {
    const additionalParametersAsJson = JSON.stringify(
      {
        storySource: transformLocationMapToIds(additionalParameters)
      },
      null,
      2
    ).trim().slice(0, -1);
    const propertyDeclaration = foundParametersProperty ? "" : "parameters: ";
    const comma = foundParametersProperty ? "" : ",";
    const newParameters = `${propertyDeclaration}${additionalParametersAsJson},${parametersSliceOfCode.substring(
      1
    )}${comma}`;
    const additionalComma = comma === "," ? "" : ",";
    const result = `${splicedSource.substring(
      0,
      indexWhereToAppend
    )}${newParameters}${additionalComma}${splicedSource.substring(indexWhereToAppend)}`;
    return result;
  }
  return source;
}
function addStorySourceParameter(key, snippet) {
  const source = sanitizeSource(snippet);
  return `${key}.parameters = { storySource: { source: ${source} }, ...${key}.parameters };`;
}
function generateSourcesInStoryParameters(source, ast, additionalParameters) {
  if (!additionalParameters || !additionalParameters.source || !additionalParameters.locationsMap) {
    return source;
  }
  const { source: sanitizedSource, locationsMap } = additionalParameters;
  const lines = sanitizedSource.split("\n");
  const suffix = Object.entries(locationsMap).reduce((acc, [exportName, location]) => {
    const exportSource = extractSource(location, lines);
    if (exportSource) {
      const generated = addStorySourceParameter(exportName, exportSource);
      return `${acc}
${generated}`;
    }
    return acc;
  }, "");
  return suffix ? `${source}

${suffix}` : source;
}

// src/abstract-syntax-tree/default-options.js
var defaultOptions = {
  prettierConfig: {
    printWidth: 100,
    tabWidth: 2,
    bracketSpacing: true,
    trailingComma: "es5",
    singleQuote: true
  },
  uglyCommentsRegex: [/^eslint-.*/, /^global.*/]
};
var default_options_default = defaultOptions;

// src/abstract-syntax-tree/inject-decorator.js
function extendOptions(source, comments, filepath, options) {
  return {
    ...default_options_default,
    ...options,
    source,
    comments,
    filepath
  };
}
function inject(source, filepath, options = {}, log = (message) => {
}) {
  const { injectDecorator = true, injectStoryParameters = false } = options;
  const obviouslyNotCode = ["md", "txt", "json"].includes(options.parser);
  let parser = null;
  try {
    parser = parsers_default(options.parser || filepath);
  } catch (e) {
    log(new Error(`(not fatal, only impacting storysource) Could not load a parser (${e})`));
  }
  if (obviouslyNotCode || !parser) {
    return {
      source,
      storySource: {},
      addsMap: {},
      changed: false
    };
  }
  const ast = parser.parse(source);
  const {
    changed,
    source: cleanedSource,
    comments,
    exportTokenFound
  } = injectDecorator === true ? generateSourceWithDecorators(source, ast) : generateSourceWithoutDecorators(source, ast);
  const storySource = generateStorySource(extendOptions(source, comments, filepath, options));
  const newAst = parser.parse(storySource);
  const addsMap = generateStoriesLocationsMap(newAst, []);
  let newSource = cleanedSource;
  if (exportTokenFound) {
    const cleanedSourceAst = parser.parse(cleanedSource);
    if (injectStoryParameters) {
      newSource = generateSourcesInStoryParameters(cleanedSource, cleanedSourceAst, {
        source: storySource,
        locationsMap: addsMap
      });
    } else {
      newSource = generateSourcesInExportedParameters(cleanedSource, cleanedSourceAst, {
        source: storySource,
        locationsMap: addsMap
      });
    }
  }
  if (!changed && Object.keys(addsMap || {}).length === 0) {
    return {
      source: newSource,
      storySource,
      addsMap: {},
      changed
    };
  }
  return {
    source: newSource,
    storySource,
    addsMap,
    changed
  };
}
var inject_decorator_default = inject;

// src/dependencies-lookup/readAsObject.js
function readAsObject(classLoader, inputSource, mainFile) {
  const options = classLoader.getOptions();
  const result = inject_decorator_default(
    inputSource,
    classLoader.resourcePath,
    {
      ...options,
      parser: options.parser || classLoader.extension
    },
    classLoader.emitWarning.bind(classLoader)
  );
  const sourceJson = sanitizeSource(result.storySource || inputSource);
  const addsMap = result.addsMap || {};
  const source = mainFile ? result.source : inputSource;
  return new Promise(
    (resolve) => resolve({
      source,
      sourceJson,
      addsMap
    })
  );
}
function readStory(classLoader, inputSource) {
  return readAsObject(classLoader, inputSource, true);
}

// src/build.js
async function transform(inputSource) {
  const sourceObject = await readStory(this, inputSource);
  if (!sourceObject.source || sourceObject.source.length === 0) {
    return inputSource;
  }
  const { source, sourceJson, addsMap } = sourceObject;
  const rawSource = await (0, import_promises.readFile)(this.resourcePath, "utf-8");
  const rawJson = sanitizeSource(rawSource);
  const preamble = `
    /* eslint-disable */
    // @ts-nocheck
    // @ts-expect-error (Converted from ts-ignore)
    var __STORY__ = ${rawJson};
    // @ts-expect-error (Converted from ts-ignore)
    var __LOCATIONS_MAP__ = ${JSON.stringify(addsMap, null, 2).trim()};
    `;
  return `${preamble}
${source}`;
}

// src/index.ts
var src_default = transform;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  extractSource
});

Documentation

No need to update docs since its a fix.

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>

name before after diff z %
createSize 0 B 0 B 0 B - -
generateSize 77.7 MB 77.7 MB 0 B 1.36 0%
initSize 136 MB 136 MB 0 B 0.49 0%
diffSize 58.4 MB 58.4 MB 0 B 0.12 0%
buildSize 7.24 MB 7.24 MB 0 B 1.34 0%
buildSbAddonsSize 1.88 MB 1.88 MB 0 B - 0%
buildSbCommonSize 195 kB 195 kB 0 B - 0%
buildSbManagerSize 1.86 MB 1.86 MB 0 B 1.61 0%
buildSbPreviewSize 0 B 0 B 0 B - -
buildStaticSize 0 B 0 B 0 B - -
buildPrebuildSize 3.93 MB 3.93 MB 0 B 1.61 0%
buildPreviewSize 3.3 MB 3.3 MB 0 B -0.77 0%
testBuildSize 0 B 0 B 0 B - -
testBuildSbAddonsSize 0 B 0 B 0 B - -
testBuildSbCommonSize 0 B 0 B 0 B - -
testBuildSbManagerSize 0 B 0 B 0 B - -
testBuildSbPreviewSize 0 B 0 B 0 B - -
testBuildStaticSize 0 B 0 B 0 B - -
testBuildPrebuildSize 0 B 0 B 0 B - -
testBuildPreviewSize 0 B 0 B 0 B - -
name before after diff z %
createTime 7s 6.3s -757ms -1 -12%
generateTime 21.6s 18.2s -3s -392ms -1.65 🔰-18.5%
initTime 15.4s 12.4s -2s -954ms -1.72 🔰-23.7%
buildTime 8.9s 7.8s -1s -170ms -1.65 🔰-15%
testBuildTime 0ms 0ms 0ms - -
devPreviewResponsive 4.4s 5.2s 816ms 0.07 15.5%
devManagerResponsive 3.3s 3.9s 560ms 0.1 14.2%
devManagerHeaderVisible 592ms 630ms 38ms -0.17 6%
devManagerIndexVisible 618ms 669ms 51ms -0.16 7.6%
devStoryVisibleUncached 1.7s 1.9s 165ms 0.41 8.5%
devStoryVisible 617ms 667ms 50ms -0.18 7.5%
devAutodocsVisible 511ms 552ms 41ms -0.08 7.4%
devMDXVisible 477ms 564ms 87ms 0.06 15.4%
buildManagerHeaderVisible 555ms 589ms 34ms -0.49 5.8%
buildManagerIndexVisible 631ms 693ms 62ms -0.36 8.9%
buildStoryVisible 513ms 559ms 46ms -0.37 8.2%
buildAutodocsVisible 432ms 458ms 26ms -0.25 5.7%
buildMDXVisible 398ms 446ms 48ms -0.43 10.8%

Greptile Summary

Here's my efficient review of the pull request:

Updates Prettier parser imports in source-loader to use namespace imports (import * as) instead of default imports, fixing compatibility with Prettier v3 and restoring proper source code display in the storysource addon.

  • Fixed code/lib/source-loader/src/abstract-syntax-tree/parsers/parser-flow.js to use namespace import for Flow parser
  • Fixed code/lib/source-loader/src/abstract-syntax-tree/parsers/parser-js.js to use namespace import for Babel parser
  • Fixed code/lib/source-loader/src/abstract-syntax-tree/parsers/parser-ts.js to use namespace import for TypeScript parser
  • Resolves "Cannot read properties of undefined (reading 'parsers')" error that prevented source code display
  • Fixes issue [Bug]: addon-storysource doesn't display the full source #29478 where storysource addon wasn't displaying full source code

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 file(s) reviewed, 1 comment(s)
Edit PR Review Bot Settings | Greptile

@@ -1,4 +1,4 @@
import parseFlow from 'prettier/plugins/flow';
import * as parseFlow from 'prettier/plugins/flow';

function parse(source) {
return parseFlow.parsers.flow.parse(source);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Unlike the JS and TS parsers, this parser has no error handling around parse() calls. Consider adding try/catch blocks for consistency.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current error handling is the reason why the TypeError went under the radar in the first place, so I'm not sure if we should keep it or not. I'd rather leave this decision to a maintainer.

@storybook-pr-benchmarking
Copy link

storybook-pr-benchmarking bot commented Nov 25, 2024

Package Benchmarks

Commit: 4c7ca98, ran on 18 December 2024 at 09:10:19 UTC

No significant changes detected, all good. 👏

@slax57
Copy link
Author

slax57 commented Nov 27, 2024

I see some CI checks are failing, but looking at the logs I can't see an obvious link with the changes I made. Should I worry about them?

@fzaninotto
Copy link

Hey guys, any news on this one? It's a simple fix for a very annoying problem.

@slax57
Copy link
Author

slax57 commented Dec 18, 2024

Friendly bump 🙂

Is there something I can do to help review (and hopefully merge) this PR?

@yannbf yannbf assigned kasperpeulen and unassigned yannbf Dec 24, 2024
@yannbf yannbf requested a review from kasperpeulen December 24, 2024 14:50
@yannbf
Copy link
Member

yannbf commented Dec 24, 2024

@kasperpeulen would you mind reviewing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: addon-storysource doesn't display the full source
5 participants