Upgrade to Pro — share decks privately, control downloads, hide ads and more …

접근성 작업을 도와주는 ESLint plugin 개발기

kakao
November 01, 2024

접근성 작업을 도와주는 ESLint plugin 개발기

팀의 접근성 개선 작업 중, “Unit test나 Storybook 없이 접근성 위반을 바로 확인할 수는 없을까?“라는 고민이 들었습니다. 이를 해결하기 위해 접근성 위반 사항을 자동으로 알려주는 ESLint 플러그인을 개발한 경험을 공유합니다.

발표자 : eddie.ch
모빌리티에서 셔틀과 버스대절 서비스를 개발하고 있습니다.

kakao

November 01, 2024
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. ੽Ӕࢿѐࢶ BSJB - MBCFM BSJB - DIFDLFE BSJB - DPOUSPMT

    BSJB - FYQBOEFE BSJB - NPEBM BSJB - QSFTTFE BSJB - EJTBCMFE BSJB - IJEEFO
  2. )UNMীחMBOH੉੓যঠೣ ߡౡীఋੑਸԙࠢৈঠೣ BMUীࢎ૓ ੉޷૑ۄחӖ੗оٜযоݶউؽ JNHীBMUࣘࢿ੉੓যঠೣ झ৬੉೐ীݥ୶חӝמ੉੓যঠೣ *OMJOFࠗݽীCMPDL੗ध੉য়ݶউؽ ߓ҃਺ਸՍחӝמ੉੓যঠೣ ੋ੽࢚࢝੄ݺب؀࠺ਯ਷੉যঠೣ UPBTU੄दрਸ੿ೡࣻ੓যঠೣ

    ୡ׼ഥ੉࢚ߣ૶੉ח௑బஎܳನೣೞݶউؽ ੗زਵ۽ܻࣗо੤ࢤY @@CMBOLоয়ݶ࢜ହৌܿ੉ನೣغযঠೣ ઱ઁաݾ੸ਸઁݾਸನೣ೧ঠೣ 4UBUJDৃܻݢ౟ীੋఠ۩࣌੉ٜযоݶউؽ ೻٬җۨ੉࠶ীח઱ઁ৬ݾ੸ਸನೣ೧ঠೣ ઱ઁաݾ੸ਸઁݾਸನೣ೧ঠೣ NFEJBחDBQUJPO੉੓যঠೣ MBCFM਷GPSо೙ਃೣ JGSBNF਷UJUMF੉೙ਃೣ ఫझ౟৬ఫझ౟੉޷૑੄दп੸಴അਸਤೠݺب؀࠺ਯ਷୭ࣗೠ੉࢚غযঠೣ ఫझ౟௑బஎח੉೧ೞҊ੍ਸࣻ੓যঠೣ ডয੄ࠄ٪݈੉ա੄޷ܳੋधೡࣻ੓חݫழפ્ਸઁҕ೧ঠೣ ࢎਊ੗ੋఠಕ੉झҳࢿਃࣗоನழझܳ߉਷҃਋ݓۅ߸҃ਸୡې೧ࢲחউؽ ੑ۱য়ܨо੗زਵ۽х૑ػ׮ݶ ࢎਊ੗ীѱ੉ܳঌ۰઱Ҋয়ܨী؀ೠࢸݺఫझ౟о೙ਃ
  3. )UNMীחMBOH੉੓যঠೣ ߡౡীఋੑਸԙࠢৈঠೣ BMUীࢎ૓ ੉޷૑ۄחӖ੗оٜযоݶউؽ JNHীBMUࣘࢿ੉੓যঠೣ झ৬੉೐ীݥ୶חӝמ੉੓যঠೣ *OMJOFࠗݽীCMPDL੗ध੉য়ݶউؽ ߓ҃਺ਸՍחӝמ੉੓যঠೣ ੋ੽࢚࢝੄ݺب؀࠺ਯ਷੉যঠೣ UPBTU੄दрਸ੿ೡࣻ੓যঠೣ

    ୡ׼ഥ੉࢚ߣ૶੉ח௑బஎܳನೣೞݶউؽ ੗زਵ۽ܻࣗо੤ࢤY @@CMBOLоয়ݶ࢜ହৌܿ੉ನೣغযঠೣ ઱ઁաݾ੸ਸઁݾਸನೣ೧ঠೣ 4UBUJDৃܻݢ౟ীੋఠ۩࣌੉ٜযоݶউؽ ೻٬җۨ੉࠶ীח઱ઁ৬ݾ੸ਸನೣ೧ঠೣ ઱ઁաݾ੸ਸઁݾਸನೣ೧ঠೣ NFEJBחDBQUJPO੉੓যঠೣ MBCFM਷GPSо೙ਃೣ JGSBNF਷UJUMF੉೙ਃೣ ఫझ౟৬ఫझ౟੉޷૑੄दп੸಴അਸਤೠݺب؀࠺ਯ਷୭ࣗೠ੉࢚غযঠೣ ఫझ౟௑బஎח੉೧ೞҊ੍ਸࣻ੓যঠೣ ডয੄ࠄ٪݈੉ա੄޷ܳੋधೡࣻ੓חݫழפ્ਸઁҕ೧ঠೣ ࢎਊ੗ੋఠಕ੉झҳࢿਃࣗоನழझܳ߉਷҃਋ݓۅ߸҃ਸୡې೧ࢲחউؽ ੑ۱য়ܨо੗زਵ۽х૑ػ׮ݶ ࢎਊ੗ীѱ੉ܳঌ۰઱Ҋয়ܨী؀ೠࢸݺఫझ౟о೙ਃ
  4. export default { meta: { docs: { url: '', description:

    ``, }, }, create: (context) => { return {} } }
  5. export default { meta: { docs: { url: '', description:

    ``, }, }, create: (context) => { return {} } }
  6. export default { meta: { docs: { url: '', description:

    ``, }, }, create: (context) => { return {} } }
  7. export default { meta: { docs: { url: '', description:

    ``, }, }, create: (context) => { return { JSXElement: (node) => {} } } }
  8. export default { create: (context) => { const elementType =

    getElementType(context) return { JSXElement: (node) => {} } } }
  9. export default { create: (context) => { const elementType =

    getElementType(context) return { JSXElement: (node) => { const element = node.openingElement const nodeType = elementType(element) // div, button, Temp if (isInlineElement(nodeType)) {} } } } }
  10. export default { create: (context) => { const elementType =

    getElementType(context) return { JSXElement: (node) => { const element = node.openingElement const nodeType = elementType(element) if (isInlineElement(nodeType)) { for (let i = 0; i < node?.children.length; i++) { // div, span, button, Temp, … const childNodeName = getChildNodeName(node, i) } } } } } }
  11. export default { create: (context) => { const elementType =

    getElementType(context) return { JSXElement: (node) => { const element = node.openingElement const nodeType = elementType(element) if (isInlineElement(nodeType)) { for (let i = 0; i < node?.children.length; i++) { const childNodeName = getChildNodeName(node, i) if (isBlockElement(childNodeName)) { context.report({ node, message: errorMessage }) } } } } } } }
  12. const Styled = { Temp: styled.div`` } const Button =

    () => { return ( <button> <Styled.Temp /> // div </button> ) }
  13. const Styled = { Temp: styled.div`` } const Button =

    () => { return ( <button> <Styled.Temp /> // div </button> ) }
  14. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    // styled.div``, styled(Temp)`` let scName = node.parent?.id?.name if (!scName) { if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } } }, })
  15. const Styled = { Temp: styled.div`` } const { Temp

    } = Styled const Temp = styled.div``
  16. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    let scName = node.parent?.id?.name if (!scName) { // cont Styled = { Temp: styled.div`` }, const { Temp } = Styled if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } } }, })
  17. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    let scName = node.parent?.id?.name // const Temp = styled.div`` if (!scName) { if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } } }, })
  18. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    let scName = node.parent?.id?.name if (!scName) { if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } } }, })
  19. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    let scName = node.parent?.id?.name if (!scName) { if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { // const { Temp } = Styled styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } } }, })
  20. const collectStyledComponents = (styledComponentsDict, context, name) => ({ TaggedTemplateExpression(node) {

    let scName = node.parent?.id?.name if (!scName) { if (isPlainSTE(node)) scName = `${node.parent.parent.parent.id.name}.${node.parent.key.name}` else return } let tag = '' if (isPlainSTE(node)) { tag = node.tag.property.name if (scName.split('.')[1]) { styledComponentsDict[scName.split('.')[1]] = { name: scName.split(‘.’)[1], tag } } styledComponentsDict[scName] = { name: scName, tag } // Styled.Temp, Temp } }, })
  21. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { const { tag } = styledComponent const originalNodeName = node.openingElement.name try { node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, } rule.create(context).JSXElement(node) } finally { node.openingElement.name = originalNodeName } } else { rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  22. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { const { tag } = styledComponent const originalNodeName = node.openingElement.name try { node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, } rule.create(context).JSXElement(node) } finally { node.openingElement.name = originalNodeName } } else { rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  23. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { const { tag } = styledComponent const originalNodeName = node.openingElement.name try { node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, } rule.create(context).JSXElement(node) } finally { node.openingElement.name = originalNodeName } } else { rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  24. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { const { tag } = styledComponent const originalNodeName = node.openingElement.name try { node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, } rule.create(context).JSXElement(node) } finally { node.openingElement.name = originalNodeName } } else { // ੷੢ࣗী ೧׼ ч੉ হ׮ݶ… rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  25. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { // ੷੢ࣗী ೧׼ ч੉ ੓׮ݶ… const { tag } = styledComponent const originalNodeName = node.openingElement.name try { // ੋఠಕ੉झ ాੌ ߂ కӒ ߸҃ node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, // కӒ ߸҃ } rule.create(context).JSXElement(node)// पઁ ܙ } finally { node.openingElement.name = originalNodeName } } else { rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  26. module.exports = (context, styledComponents, rule, name) => ({ JSXElement(node) {

    try { const elementName = node.openingElement.name.name const styledComponent = styledComponents[elementName] if (styledComponent) { const { tag } = styledComponent const originalNodeName = node.openingElement.name try { node.openingElement.name = { type: 'JSXIdentifier', ...originalNodeName, name: tag, } rule.create(context).JSXElement(node) } finally { node.openingElement.name = originalNodeName } } else { rule.create(context).JSXElement(node, styledComponents) } } catch {} }, })
  27. 2"